/* 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 3 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, see . * */ #include "audio/decoders/wave.h" #include "common/debug.h" #include "common/file.h" #include "common/scummsys.h" #include "common/stream.h" #include "math/utils.h" #include "math/angle.h" #include "zvision/zvision.h" #include "zvision/graphics/render_manager.h" #include "zvision/scripting/script_manager.h" #include "zvision/scripting/effects/music_effect.h" #include "zvision/sound/midi.h" #include "zvision/sound/volume_manager.h" #include "zvision/sound/zork_raw.h" namespace ZVision { void MusicNodeBASE::setDirection(Math::Angle azimuth, uint8 magnitude) { if (_engine->getScriptManager()->getStateValue(StateKey_Qsound) >= 1) { _azimuth = azimuth; _directionality = magnitude; _balance = ((int)(127 * _azimuth.getSine()) * _directionality) / 255; } else setBalance(0); updateMixer(); } void MusicNodeBASE::setBalance(int8 balance) { _balance = balance; _azimuth.setDegrees(0); _directionality = 255; updateMixer(); } void MusicNodeBASE::updateMixer() { if (_engine->getScriptManager()->getStateValue(StateKey_Qsound) >= 1) _volumeOut = _engine->getVolumeManager()->convert(_volume, _azimuth, _directionality); // Apply game-specific volume profile and then attenuate according to azimuth else _volumeOut = _engine->getVolumeManager()->convert(_volume); // Apply game-specific volume profile and ignore azimuth outputMixer(); } MusicNode::MusicNode(ZVision *engine, uint32 key, Common::Path &filename, bool loop, uint8 volume) : MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) { _loop = loop; _volume = volume; _balance = 0; _fade = false; _fadeStartVol = volume; _fadeEndVol = 0; _fadeTime = 0; _fadeElapsed = 0; _sub = 0; _stereo = false; _loaded = false; Audio::RewindableAudioStream *audioStream = NULL; if (filename.baseName().contains(".wav")) { Common::File *file = new Common::File(); if (file->open(filename)) { audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES); } } else { audioStream = makeRawZorkStream(filename, _engine); } if (audioStream) { _stereo = audioStream->isStereo(); if (_loop) { Audio::LoopingAudioStream *loopingAudioStream = new Audio::LoopingAudioStream(audioStream, 0, DisposeAfterUse::YES); _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, loopingAudioStream, -1, _volume); } else { _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audioStream, -1, _volume); } if (_key != StateKey_NotSet) { debugC(3, kDebugSound, "setting musicnode state value to 1"); _engine->getScriptManager()->setStateValue(_key, 1); } // Change filename.raw into filename.sub Common::String subname = filename.baseName(); subname.setChar('s', subname.size() - 3); subname.setChar('u', subname.size() - 2); subname.setChar('b', subname.size() - 1); Common::Path subpath(filename.getParent().appendComponent(subname)); if (SearchMan.hasFile(subpath)) _sub = _engine->getSubtitleManager()->create(subpath, _handle); // NB automatic subtitle! _loaded = true; updateMixer(); } debugC(3, kDebugSound, "MusicNode: %d created", _key); } MusicNode::~MusicNode() { if (_loaded) _engine->_mixer->stopHandle(_handle); if (_key != StateKey_NotSet) _engine->getScriptManager()->setStateValue(_key, 2); if (_sub) _engine->getSubtitleManager()->destroy(_sub); debugC(3, kDebugSound, "MusicNode: %d destroyed", _key); } void MusicNode::outputMixer() { _engine->_mixer->setChannelBalance(_handle, _balance); _engine->_mixer->setChannelVolume(_handle, _volumeOut); } void MusicNode::setFade(int32 time, uint8 target) { _fadeStartVol = _volume; _fadeEndVol = target; _fadeElapsed = 0; _fadeTime = time <= 0 ? 0 : (uint32)time; _fade = true; } bool MusicNode::process(uint32 deltaTimeInMillis) { if (!_loaded || ! _engine->_mixer->isSoundHandleActive(_handle)) return stop(); else { if (_fade) { debugC(3, kDebugSound, "Fading music, endVol %d, startVol %d, current %d, fade time %d, elapsed time %dms", _fadeEndVol, _fadeStartVol, _volume, _fadeTime, _fadeElapsed); uint8 _newvol = 0; _fadeElapsed += deltaTimeInMillis; if ((_fadeTime <= 0) | (_fadeElapsed >= _fadeTime)) { _newvol = _fadeEndVol; _fade = false; } else { if (_fadeEndVol > _fadeStartVol) _newvol = _fadeStartVol + (_fadeElapsed * (_fadeEndVol - _fadeStartVol)) / _fadeTime; else _newvol = _fadeStartVol - (_fadeElapsed * (_fadeStartVol - _fadeEndVol)) / _fadeTime; } if (_volume != _newvol) setVolume(_newvol); } } return false; } void MusicNode::setVolume(uint8 newVolume) { if (_loaded) { debugC(4, kDebugSound, "Changing volume of music node %d from %d to %d", _key, _volume, newVolume); _volume = newVolume; updateMixer(); } } PanTrackNode::PanTrackNode(ZVision *engine, uint32 key, uint32 slot, int16 pos, uint8 mag, bool resetMixerOnDelete, bool staticScreen) : ScriptingEffect(engine, key, SCRIPTING_EFFECT_PANTRACK), _slot(slot), _sourcePos(0), _viewPos(0), _mag(mag), _width(0), _pos(pos), _staticScreen(staticScreen), _resetMixerOnDelete(resetMixerOnDelete) { debugC(3, kDebugSound, "Created PanTrackNode, key %d, slot %d", _key, _slot); process(0); // Try to set pan value for music node immediately } PanTrackNode::~PanTrackNode() { debugC(1, kDebugSound, "Deleting PanTrackNode, key %d, slot %d", _key, _slot); ScriptManager *scriptManager = _engine->getScriptManager(); ScriptingEffect *fx = scriptManager->getSideFX(_slot); if (fx && fx->getType() == SCRIPTING_EFFECT_AUDIO && _resetMixerOnDelete) { debugC(1, kDebugSound, "Resetting mixer, slot %d", _slot); MusicNodeBASE *mus = (MusicNodeBASE *)fx; mus->setBalance(0); } else debugC(1, kDebugSound, "NOT resetting mixer, slot %d", _slot); } bool PanTrackNode::process(uint32 deltaTimeInMillis) { debugC(3, kDebugSound, "Processing PanTrackNode, key %d", _key); ScriptManager *scriptManager = _engine->getScriptManager(); ScriptingEffect *fx = scriptManager->getSideFX(_slot); if (fx && fx->getType() == SCRIPTING_EFFECT_AUDIO) { MusicNodeBASE *mus = (MusicNodeBASE *)fx; if (!_staticScreen) // Original game scripted behaviour switch (_engine->getRenderManager()->getRenderTable()->getRenderState()) { case RenderTable::PANORAMA: debugC(3, kDebugSound, "PanTrackNode in panorama mode"); _width = _engine->getRenderManager()->getBkgSize().x; if (_width) { _sourcePos.setDegrees(360 * _pos / _width); _viewPos.setDegrees(360 * scriptManager->getStateValue(StateKey_ViewPos) / _width); } else { warning("Encountered zero background width whilst processing PanTrackNode in panoramic mode!"); } break; case RenderTable::FLAT: case RenderTable::TILT: default: debugC(3, kDebugSound, "PanTrackNode in FLAT/TILT mode"); _sourcePos.setDegrees(0); _viewPos.setDegrees(0); break; } else { // Used for auxiliary scripts only _sourcePos.setDegrees(_pos); _viewPos.setDegrees(0); } Math::Angle azimuth; azimuth = _sourcePos - _viewPos; debugC(3, kDebugSound, "soundPos: %f, _viewPos: %f, azimuth: %f, width %d", _sourcePos.getDegrees(), _viewPos.getDegrees(), azimuth.getDegrees(), _width); // azimuth is sound source position relative to player, clockwise from centre of camera axis to front when viewed top-down mus->setDirection(azimuth, _mag); } return false; } MusicMidiNode::MusicMidiNode(ZVision *engine, uint32 key, uint8 program, uint8 note, uint8 volume) : MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) { _volume = volume; _prog = program; _noteNumber = note; _pan = 0; _chan = _engine->getMidiManager()->getFreeChannel(); if (_chan >= 0) { updateMixer(); _engine->getMidiManager()->setProgram(_chan, _prog); _engine->getMidiManager()->noteOn(_chan, _noteNumber, _volume); } if (_key != StateKey_NotSet) _engine->getScriptManager()->setStateValue(_key, 1); } MusicMidiNode::~MusicMidiNode() { if (_chan >= 0) { _engine->getMidiManager()->noteOff(_chan); } if (_key != StateKey_NotSet) _engine->getScriptManager()->setStateValue(_key, 2); } void MusicMidiNode::setFade(int32 time, uint8 target) { } bool MusicMidiNode::process(uint32 deltaTimeInMillis) { return false; } void MusicMidiNode::setVolume(uint8 newVolume) { if (_chan >= 0) { _volume = newVolume; updateMixer(); } } void MusicMidiNode::outputMixer() { _engine->getMidiManager()->setBalance(_chan, _balance); _engine->getMidiManager()->setPan(_chan, _pan); _engine->getMidiManager()->setVolume(_chan, _volumeOut); } } // End of namespace ZVision