/* 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. * */ #include "fullpipe/fullpipe.h" #include "fullpipe/objects.h" #include "fullpipe/scene.h" #include "fullpipe/sound.h" #include "fullpipe/ngiarchive.h" #include "fullpipe/messages.h" #include "fullpipe/statics.h" #include "common/config-manager.h" #include "common/memstream.h" #include "audio/mixer.h" #include "audio/audiostream.h" #include "audio/decoders/vorbis.h" #include "audio/decoders/wave.h" namespace Fullpipe { SoundList::SoundList() { _soundItems = 0; _soundItemsCount = 0; _libHandle = 0; } SoundList::~SoundList() { for (int i = 0; i < _soundItemsCount; i++) delete _soundItems[i]; free(_soundItems); } bool SoundList::load(MfcArchive &file, char *fname) { debugC(5, kDebugLoading, "SoundList::load()"); _soundItemsCount = file.readUint32LE(); _soundItems = (Sound **)calloc(_soundItemsCount, sizeof(Sound *)); if (fname) { _libHandle = (NGIArchive *)makeNGIArchive(fname); } else { _libHandle = 0; } for (int i = 0; i < _soundItemsCount; i++) { Sound *snd = new Sound(); _soundItems[i] = snd; snd->load(file, _libHandle); } return true; } bool SoundList::loadFile(const char *fname, char *libname) { Common::File file; if (!file.open(fname)) return false; MfcArchive archive(&file); return load(archive, libname); } Sound *SoundList::getSoundItemById(int id) { if (_soundItemsCount == 0) { return _soundItems[0]->getId() != id ? 0 : _soundItems[0]; } for (int i = 0; i < _soundItemsCount; i++) { if (_soundItems[i]->getId() == id) return _soundItems[i]; } return NULL; } Sound::Sound() { _id = 0; _directSoundBuffer = 0; _soundData = 0; _objectId = 0; memset(_directSoundBuffers, 0, sizeof(_directSoundBuffers)); _description = 0; _volume = 100; _handle = new Audio::SoundHandle(); } Sound::~Sound() { freeSound(); free(_description); delete _handle; } bool Sound::load(MfcArchive &file, NGIArchive *archive) { debugC(5, kDebugLoading, "Sound::load()"); MemoryObject::load(file); _id = file.readUint32LE(); _description = file.readPascalString(); assert(g_fp->_gameProjectVersion >= 6); _objectId = file.readUint16LE(); if (archive && archive->hasFile(_memfilename)) { Common::SeekableReadStream *s = archive->createReadStreamForMember(_memfilename); _soundData = (byte *)calloc(s->size(), 1); s->read(_soundData, s->size()); delete s; } return true; } void Sound::updateVolume() { // not needed in our implementation } void Sound::setPanAndVolumeByStaticAni() { if (!_objectId) return; StaticANIObject *ani = g_fp->_currentScene->getStaticANIObject1ById(_objectId, -1); if (!ani) return; int a, b; if (ani->_ox >= g_fp->_sceneRect.left) { int par, pan; if (ani->_ox <= g_fp->_sceneRect.right) { int dx; if (ani->_oy <= g_fp->_sceneRect.bottom) { if (ani->_oy >= g_fp->_sceneRect.top) { setPanAndVolume(g_fp->_sfxVolume, 0); return; } dx = g_fp->_sceneRect.top - ani->_oy; } else { dx = ani->_oy - g_fp->_sceneRect.bottom; } par = 0; if (dx > 800) { setPanAndVolume(-3500, 0); return; } pan = -3500; a = g_fp->_sfxVolume - (-3500); b = 800 - dx; } else { int dx = ani->_ox - g_fp->_sceneRect.right; if (dx > 800) { setPanAndVolume(-3500, 0); return; } pan = -3500; par = dx * (-3500) / -800; a = g_fp->_sfxVolume - (-3500); b = 800 - dx; } int32 pp = b * a; setPanAndVolume(pan + pp / 800, par); return; } int dx = g_fp->_sceneRect.left - ani->_ox; if (dx <= 800) { int32 s = (800 - dx) * (g_fp->_sfxVolume - (-3500)); int32 p = -3500 + s / 800; if (p > g_fp->_sfxVolume) p = g_fp->_sfxVolume; setPanAndVolume(p, dx * (-3500) / 800); } else { setPanAndVolume(-3500, 0); } } void Sound::setPanAndVolume(int vol, int pan) { g_fp->_mixer->setChannelVolume(*_handle, MIN((vol + 10000) / 39, 255)); // -10000..0 g_fp->_mixer->setChannelBalance(*_handle, CLIP(pan / 78, -127, 127)); // -10000..10000 } void Sound::play(int flag) { Audio::SoundHandle *handle = getHandle(); if (g_fp->_mixer->isSoundHandleActive(*handle)) return; byte *soundData = loadData(); Common::MemoryReadStream *dataStream = new Common::MemoryReadStream(soundData, getDataSize()); Audio::RewindableAudioStream *wav = Audio::makeWAVStream(dataStream, DisposeAfterUse::YES); Audio::AudioStream *audioStream = new Audio::LoopingAudioStream(wav, (flag == 1) ? 0 : 1); g_fp->_mixer->playStream(Audio::Mixer::kSFXSoundType, handle, audioStream); } void Sound::freeSound() { stop(); free(_soundData); } int Sound::getVolume() { return g_fp->_mixer->getChannelVolume(*_handle) * 39; // 0..10000 } void Sound::stop() { g_fp->_mixer->stopHandle(*_handle); } void FullpipeEngine::setSceneMusicParameters(GameVar *gvar) { warning("STUB: FullpipeEngine::setSceneMusicParameters()"); stopSoundStream2(); #if 0 if (soundStream3) FSOUND_Stream_Stop(soundStream4); #endif if (_musicLocal) stopAllSoundStreams(); GameVar *var = gvar->getSubVarByName("MUSIC"); memset(_sceneTracks, 0, sizeof(_sceneTracks)); _numSceneTracks = 0; _sceneTrackHasSequence = false; if (!var) return; _musicGameVar = var; GameVar *tr = var->getSubVarByName("TRACKS"); if (tr) { GameVar *sub = tr->_subVars; while (sub) { if (_musicAllowed & sub->_value.intValue) { Common::strlcpy(_sceneTracks[_numSceneTracks], sub->_varName, 260); _numSceneTracks++; } sub = sub->_nextVarObj; } } _musicMinDelay = var->getSubVarAsInt("MINDELAY"); _musicMaxDelay = var->getSubVarAsInt("MAXDELAY"); _musicLocal = var->getSubVarAsInt("LOCAL"); GameVar *seq = var->getSubVarByName("SEQUENCE"); if (seq) { _sceneTrackHasSequence = true; Common::strlcpy(_trackName, seq->_value.stringValue, 2600); } if (_musicLocal) stopAllSoundStreams(); if (!_sceneTrackIsPlaying || _musicLocal) _trackStartDelay = var->getSubVarAsInt("STARTDELAY"); } void FullpipeEngine::updateTrackDelay() { _sceneTrackIsPlaying = false; _trackStartDelay = _musicMinDelay + (_musicMaxDelay - _musicMinDelay) * (_updateTicks % 10) / 9; } void FullpipeEngine::startSceneTrack() { if (_sceneTrackIsPlaying) { if (!_mixer->isSoundHandleActive(*_soundStream1)) { // Simulate end of sound callback updateTrackDelay(); } } if (!_sceneTrackIsPlaying && _numSceneTracks > 0) { if (_trackStartDelay > 0) { _trackStartDelay--; } else { int trackNum = getSceneTrack(); if (trackNum == -1) { strcpy(_sceneTracksCurrentTrack, "silence"); _trackStartDelay = 2880; _sceneTrackIsPlaying = 0; } else { strcpy(_sceneTracksCurrentTrack, _sceneTracks[trackNum]); startSoundStream1(_sceneTracksCurrentTrack); _sceneTrackIsPlaying = true; } } } } int FullpipeEngine::getSceneTrack() { int res; if (_sceneTrackHasSequence) { int num = _musicGameVar->getSubVarAsInt("TRACKS"); if (_trackName[num + 1] == 's') { // 'silence' res = -1; } else { res = _trackName[num + 1] - '0'; if (res < 0 || res >= _numSceneTracks) res = 0; } int track = num + 1; if (!_trackName[num + 2]) track = 0; _musicGameVar->setSubVarAsInt("TRACKS", track); } else { res = _numSceneTracks * (_updateTicks % 10) / 10; } return res; } void FullpipeEngine::startSoundStream1(char *trackName) { stopAllSoundStreams(); #ifdef USE_VORBIS if (_mixer->isSoundHandleActive(*_soundStream1)) return; Common::File *track = new Common::File(); if (!track->open(trackName)) { warning("Could not open %s", trackName); delete track; return; } Audio::RewindableAudioStream *ogg = Audio::makeVorbisStream(track, DisposeAfterUse::YES); _mixer->playStream(Audio::Mixer::kMusicSoundType, _soundStream1, ogg); #endif } void FullpipeEngine::stopAllSounds() { _mixer->stopHandle(*_soundStream1); _mixer->stopHandle(*_soundStream2); _mixer->stopHandle(*_soundStream3); _mixer->stopHandle(*_soundStream4); _stream2playing = false; } void FullpipeEngine::toggleMute() { if (_soundEnabled) { _sfxVolume = _sfxVolume != -10000 ? -10000 : 0; updateSoundVolume(); } } void FullpipeEngine::playSound(int id, int flag) { Sound *sound = 0; for (int i = 0; i < _currSoundListCount; i++) { sound = _currSoundList1[i]->getSoundItemById(id); if (sound) break; } if (!sound) { warning("playSound: Can't find sound with ID %d", id); return; } sound->play(flag); } void FullpipeEngine::playTrack(GameVar *sceneVar, const char *name, bool delayed) { warning("STUB: FullpipeEngine::playTrack(var, %s, %d)", name, delayed); stopSoundStream2(); #if 0 if (soundStream3) FSOUND_Stream_Stop(soundStream4); #endif if (_musicLocal) stopAllSoundStreams(); GameVar *var = sceneVar->getSubVarByName(name); memset(_sceneTracks, 0, sizeof(_sceneTracks)); _numSceneTracks = 0; _sceneTrackHasSequence = false; if (!var) return; _musicGameVar = var; GameVar *tr = var->getSubVarByName("TRACKS"); if (tr) { GameVar *sub = tr->_subVars; while (sub) { if (_musicAllowed & sub->_value.intValue) { Common::strlcpy(_sceneTracks[_numSceneTracks], sub->_varName, 260); _numSceneTracks++; } sub = sub->_nextVarObj; } } _musicMinDelay = var->getSubVarAsInt("MINDELAY"); _musicMaxDelay = var->getSubVarAsInt("MAXDELAY"); _musicLocal = var->getSubVarAsInt("LOCAL"); GameVar *seq = var->getSubVarByName("SEQUENCE"); if (seq) { _sceneTrackHasSequence = true; Common::strlcpy(_trackName, seq->_value.stringValue, 2600); } if (delayed) { if (_sceneTrackIsPlaying && _numSceneTracks == 1) { if (strcmp(_sceneTracksCurrentTrack, _sceneTracks[0])) stopAllSoundStreams(); } _trackStartDelay = var->getSubVarAsInt("STARTDELAY"); } } void global_messageHandler_handleSound(ExCommand *cmd) { if (!g_fp->_soundEnabled) return; Sound *snd = 0; for (int i = 0; i < g_fp->_currSoundListCount; i++) if ((snd = g_fp->_currSoundList1[i]->getSoundItemById(cmd->_messageNum)) != NULL) break; if (!snd) return; if (cmd->_field_14 & 1) { if (!g_fp->_flgSoundList && (cmd->_field_14 & 4)) snd->freeSound(); snd->updateVolume(); if (snd->_objectId && g_fp->_currentScene->getStaticANIObject1ById(snd->_objectId, -1)) snd->setPanAndVolumeByStaticAni(); else snd->setPanAndVolume(g_fp->_sfxVolume, 0); if (snd->getVolume() > -3500) snd->play(cmd->_param); } else if (cmd->_field_14 & 2) { snd->stop(); } } void FullpipeEngine::stopSoundStream2() { _stream2playing = false; if (_mixer->isSoundHandleActive(*_soundStream3)) { _mixer->stopHandle(*_soundStream2); _mixer->stopHandle(*_soundStream3); } } void FullpipeEngine::stopAllSoundStreams() { warning("STUB: stopAllSoundStreams()"); // TODO: Differences from stopAllSounds() _mixer->stopAll(); } void FullpipeEngine::stopAllSoundInstances(int id) { for (int i = 0; i < _currSoundListCount; i++) { Sound *sound = _currSoundList1[i]->getSoundItemById(id); if (sound) sound->stop(); } } void FullpipeEngine::updateSoundVolume() { ConfMan.setInt("sfx_volume", MAX((_sfxVolume + 10000) / 39, 255)); syncSoundSettings(); for (int i = 0; i < _currSoundListCount; i++) for (int j = 0; j < _currSoundList1[i]->getCount(); j++) { _currSoundList1[i]->getSoundByIndex(j)->setPanAndVolume(_sfxVolume, 0); } } void FullpipeEngine::setMusicVolume(int vol) { _musicVolume = vol; ConfMan.setInt("music_volume", _musicVolume); syncSoundSettings(); } } // End of namespace Fullpipe