/* 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 "ultima/ultima8/audio/audio_process.h" #include "ultima/ultima8/usecode/uc_machine.h" #include "ultima/ultima8/games/game_data.h" #include "ultima/ultima8/audio/speech_flex.h" #include "ultima/ultima8/audio/audio_mixer.h" #include "ultima/ultima8/world/get_object.h" #include "ultima/ultima8/world/item.h" #include "ultima/ultima8/world/camera_process.h" namespace Ultima { namespace Ultima8 { // p_dynamic_class stuff DEFINE_RUNTIME_CLASSTYPE_CODE(AudioProcess) AudioProcess *AudioProcess::_theAudioProcess = nullptr; const uint32 AudioProcess::PITCH_SHIFT_NONE = 0x10000; AudioProcess::AudioProcess(void) : _paused(0) { _theAudioProcess = this; _type = 1; // persistent } AudioProcess::~AudioProcess(void) { _theAudioProcess = nullptr; } bool AudioProcess::calculateSoundVolume(ObjId objId, int16 &volume, int8 &balance) const { Item *item = getItem(objId); if (!item) { volume = 255; balance = 0; return false; } // Need to get items relative coords from avatar Point3 a = CameraProcess::GetCameraLocation(); Point3 i = item->getLocationAbsolute(); i.x -= a.x; i.y -= a.y; i.z -= a.z; // // Convert to screenspace // // Note that this should also correct for Crusader too. // int x = (i.x - i.y) / 4; int y = (i.x + i.y) / 8 - i.z; // Fall off over 350 pixels int limit = 350 * 350; int dist = limit - (x * x + y * y); dist = (dist * 256) / limit; volume = CLIP(dist, 0, 255); // range is 0 ~ 255 int b = (x * 127) / 160; balance = CLIP(b, -127, 127); // range is -127 ~ +127 return true; } void AudioProcess::run() { AudioMixer *mixer = AudioMixer::get_instance(); // Update the channels Std::list::iterator it; for (it = _sampleInfo.begin(); it != _sampleInfo.end();) { bool finished = false; if (!mixer->isPlaying(it->_channel)) { if (it->_sfxNum == -1) finished = !continueSpeech(*it); else finished = true; } if (it->_loops == -1) { // check if an ever-looping sfx for an item has left the // fast area.. if so we are "finished". Item *item = getItem(it->_objId); if (item && !item->hasFlags(Item::FLG_FASTAREA) && mixer->isPlaying(it->_channel)) { finished = true; mixer->stopSample(it->_channel); } } if (finished) it = _sampleInfo.erase(it); else { if (it->_sfxNum != -1 && it->_objId) { calculateSoundVolume(it->_objId, it->_calcVol, it->_balance); } mixer->setVolume(it->_channel, (it->_calcVol * it->_volume) / 256, it->_balance); ++it; } } } bool AudioProcess::continueSpeech(SampleInfo &si) { assert(si._sfxNum == -1); SpeechFlex *speechflex; speechflex = GameData::get_instance()->getSpeechFlex(si._priority); if (!speechflex) return false; if (si._curSpeechEnd >= si._barked.size()) return false; si._curSpeechStart = si._curSpeechEnd; int index = speechflex->getIndexForPhrase(si._barked, si._curSpeechStart, si._curSpeechEnd); if (!index) return false; AudioSample *sample = speechflex->getSample(index); if (!sample) return false; // hack to prevent playSample from deleting 'si' si._channel = -1; int channel = playSample(sample, 200, 0, true); if (channel == -1) return false; si._channel = channel; return true; } void AudioProcess::saveData(Common::WriteStream *ws) { Process::saveData(ws); ws->writeByte(static_cast(_sampleInfo.size())); for (const auto &si : _sampleInfo) { ws->writeUint16LE(si._sfxNum); ws->writeUint16LE(si._priority); ws->writeUint16LE(si._objId); ws->writeUint16LE(si._loops); ws->writeUint32LE(si._pitchShift); ws->writeUint16LE(si._volume); if (si._sfxNum == -1) { // Speech ws->writeUint32LE(static_cast(si._barked.size())); ws->write(si._barked.c_str(), static_cast(si._barked.size())); } } } bool AudioProcess::loadData(Common::ReadStream *rs, uint32 version) { if (!Process::loadData(rs, version)) return false; uint32 count = rs->readByte(); while (count--) { int16 sfxNum = rs->readUint16LE(); int16 priority = rs->readUint16LE(); int16 objId = rs->readUint16LE(); int16 loops = rs->readUint16LE(); uint32 pitchShift = rs->readUint32LE(); uint16 volume = rs->readUint16LE(); if (sfxNum != -1) { // SFX int16 calcVol = 0; int8 balance = 0; if (objId != 0) { calcVol = 255; } // Note: Small inconsistency for backward compatibility - reload ambient sounds as non-ambient. playSFX(sfxNum, priority, objId, loops, false, pitchShift, volume, calcVol, balance, false); } else { // Speech uint32 slen = rs->readUint32LE(); char *buf = new char[slen + 1]; rs->read(buf, slen); buf[slen] = 0; Std::string text = buf; delete[] buf; playSpeech(text, priority, objId, pitchShift, volume); } } return true; } int AudioProcess::playSample(AudioSample *sample, int priority, int loops, bool isSpeech, uint32 pitchShift, int16 volume, int8 balance, bool ambient) { AudioMixer *mixer = AudioMixer::get_instance(); int channel = mixer->playSample(sample, loops, priority, isSpeech, pitchShift, volume, balance, ambient); if (channel == -1) return channel; // Erase old sample using channel (if any) Std::list::iterator it; for (it = _sampleInfo.begin(); it != _sampleInfo.end();) { if (it->_channel == channel) { it = _sampleInfo.erase(it); } else { ++it; } } return channel; } void AudioProcess::playSFX(int sfxNum, int priority, ObjId objId, int loops, bool no_duplicates, uint32 pitchShift, uint16 volume, int16 calcVol, int8 balance, bool ambient) { SoundFlex *soundflx = GameData::get_instance()->getSoundFlex(); AudioMixer *mixer = AudioMixer::get_instance(); if (no_duplicates) { Std::list::iterator it; for (it = _sampleInfo.begin(); it != _sampleInfo.end();) { if (it->_sfxNum == sfxNum && it->_objId == objId && it->_loops == loops) { // Exactly the same (and playing) so just return //if (it->priority == priority) if (mixer->isPlaying(it->_channel)) { debug(1, "Sound %d already playing on obj %u", sfxNum, objId); return; } else { it = _sampleInfo.erase(it); continue; } } ++it; } } AudioSample *sample = soundflx->getSample(sfxNum); if (!sample) return; if (calcVol == -1) { calculateSoundVolume(objId, calcVol, balance); } int channel = playSample(sample, priority, loops, false, pitchShift, (calcVol * volume) / 256, balance, ambient); if (channel == -1) return; // Update list _sampleInfo.push_back(SampleInfo(sfxNum, priority, objId, loops, channel, pitchShift, volume, calcVol, balance, ambient)); } void AudioProcess::stopSFX(int sfxNum, ObjId objId) { AudioMixer *mixer = AudioMixer::get_instance(); Std::list::iterator it; for (it = _sampleInfo.begin(); it != _sampleInfo.end();) { if ((sfxNum == -1 || it->_sfxNum == sfxNum) && it->_objId == objId) { if (mixer->isPlaying(it->_channel)) mixer->stopSample(it->_channel); it = _sampleInfo.erase(it); } else { ++it; } } } bool AudioProcess::isSFXPlaying(int sfxNum) { AudioMixer *mixer = AudioMixer::get_instance(); for (const auto &si : _sampleInfo) { if (si._sfxNum == sfxNum && mixer->isPlaying(si._channel)) return true; } return false; } bool AudioProcess::isSFXPlayingForObject(int sfxNum, ObjId objId) { AudioMixer *mixer = AudioMixer::get_instance(); for (const auto &si : _sampleInfo) { if ((si._sfxNum == sfxNum || sfxNum == -1) && (objId == si._objId) && mixer->isPlaying(si._channel)) return true; } return false; } void AudioProcess::setVolumeSFX(int sfxNum, uint8 volume) { AudioMixer *mixer = AudioMixer::get_instance(); for (auto &si : _sampleInfo) { if (si._sfxNum == sfxNum && si._sfxNum != -1) { si._volume = volume; calculateSoundVolume(si._objId, si._calcVol, si._balance); mixer->setVolume(si._channel, (si._calcVol * si._volume) / 256, si._balance); } } } void AudioProcess::setVolumeForObjectSFX(ObjId objId, int sfxNum, uint8 volume) { AudioMixer *mixer = AudioMixer::get_instance(); for (auto &si : _sampleInfo) { if (si._sfxNum == sfxNum && si._sfxNum != -1 && objId == si._objId) { si._volume = volume; calculateSoundVolume(si._objId, si._calcVol, si._balance); mixer->setVolume(si._channel, (si._calcVol * si._volume) / 256, si._balance); } } } // // Speech // bool AudioProcess::playSpeech(const Std::string &barked, int shapeNum, ObjId objId, uint32 pitchShift, uint16 volume) { SpeechFlex *speechflex = GameData::get_instance()->getSpeechFlex(shapeNum); if (!speechflex) return false; AudioMixer *mixer = AudioMixer::get_instance(); Std::list::iterator it; for (it = _sampleInfo.begin(); it != _sampleInfo.end();) { if (it->_sfxNum == -1 && it->_barked == barked && it->_priority == shapeNum && it->_objId == objId) { if (mixer->isPlaying(it->_channel)) { debug(1, "Speech already playing"); return true; } else { it = _sampleInfo.erase(it); continue; } } ++it; } uint32 speech_start = 0; uint32 speech_end; int index = speechflex->getIndexForPhrase(barked, speech_start, speech_end); if (!index) return false; AudioSample *sample = speechflex->getSample(index); if (!sample) return false; int channel = playSample(sample, 200, 0, true, pitchShift, volume, volume); if (channel == -1) return false; // Update list _sampleInfo.push_back(SampleInfo(barked, shapeNum, objId, channel, speech_start, speech_end, pitchShift, volume, 255, 0, false)); return true; } uint32 AudioProcess::getSpeechLength(const Std::string &barked, int shapenum) const { SpeechFlex *speechflex = GameData::get_instance()->getSpeechFlex(shapenum); if (!speechflex) return 0; return speechflex->getSpeechLength(barked); } void AudioProcess::stopSpeech(const Std::string &barked, int shapenum, ObjId objId) { AudioMixer *mixer = AudioMixer::get_instance(); Std::list::iterator it; for (it = _sampleInfo.begin(); it != _sampleInfo.end();) { if (it->_sfxNum == -1 && it->_priority == shapenum && it->_objId == objId && it->_barked == barked) { if (mixer->isPlaying(it->_channel)) mixer->stopSample(it->_channel); it = _sampleInfo.erase(it); } else { ++it; } } } bool AudioProcess::isSpeechPlaying(const Std::string &barked, int shapeNum) { Std::list::iterator it; for (auto &si : _sampleInfo) { if (si._sfxNum == -1 && si._priority == shapeNum && si._barked == barked) { return true; } } return false; } void AudioProcess::pauseAllSamples() { _paused++; if (_paused != 1) return; AudioMixer *mixer = AudioMixer::get_instance(); Std::list::iterator it; for (it = _sampleInfo.begin(); it != _sampleInfo.end();) { if (mixer->isPlaying(it->_channel)) { mixer->setPaused(it->_channel, true); ++it; } else { it = _sampleInfo.erase(it); } } } void AudioProcess::unpauseAllSamples() { _paused--; if (_paused != 0) return; AudioMixer *mixer = AudioMixer::get_instance(); Std::list::iterator it; for (it = _sampleInfo.begin(); it != _sampleInfo.end();) { if (mixer->isPlaying(it->_channel)) { mixer->setPaused(it->_channel, false); ++it; } else { it = _sampleInfo.erase(it); } } } void AudioProcess::stopAllExceptSpeech() { AudioMixer *mixer = AudioMixer::get_instance(); Std::list::iterator it; for (it = _sampleInfo.begin(); it != _sampleInfo.end();) { if (it->_barked.empty()) { if (mixer->isPlaying(it->_channel)) mixer->stopSample(it->_channel); it = _sampleInfo.erase(it); } else { ++it; } } } // // Intrinsics // uint32 AudioProcess::I_playSFX(const uint8 *args, unsigned int argsize) { ARG_SINT16(sfxNum); int16 priority = 0x60; if (argsize >= 4) { ARG_SINT16(priority_); priority = priority_; } ObjId objId = 0; if (argsize == 6) { ARG_OBJID(objectId); objId = objectId; } AudioProcess *ap = AudioProcess::get_instance(); if (ap) ap->playSFX(sfxNum, priority, objId, 0); else warning("No AudioProcess"); return 0; } uint32 AudioProcess::I_playAmbientSFX(const uint8 *args, unsigned int argsize) { ARG_SINT16(sfxNum); int16 priority = 0x60; if (argsize >= 4) { ARG_SINT16(priority_); priority = priority_; } ObjId objId = 0; if (argsize == 6) { ARG_OBJID(objectId); objId = objectId; } AudioProcess *ap = AudioProcess::get_instance(); if (ap) ap->playSFX(sfxNum, priority, objId, -1, true, PITCH_SHIFT_NONE, 0xff, true); else warning("No AudioProcess"); return 0; } uint32 AudioProcess::I_playSFXCru(const uint8 *args, unsigned int argsize) { ARG_ITEM_FROM_PTR(item) ARG_SINT16(sfxNum); if (!item) { warning("I_playSFXCru: Couldn't get item %d", id_item); } else { AudioProcess *ap = AudioProcess::get_instance(); if (ap) { // Crusader stops any existing item sounds before starting the next. ap->stopSFX(-1, item->getObjId()); ap->playSFX(sfxNum, 0x10, item->getObjId(), 0, true, PITCH_SHIFT_NONE, 0x80, false); } else { warning("I_playSFXCru Error: No AudioProcess"); } } return 0; } uint32 AudioProcess::I_playAmbientSFXCru(const uint8 *args, unsigned int argsize) { // Similar to I_playAmbientSFX, but the params are different. ARG_ITEM_FROM_PTR(item) ARG_SINT16(sfxNum); if (!item) { warning("I_playAmbientSFXCru: Couldn't get item %d", id_item); } else { AudioProcess *ap = AudioProcess::get_instance(); if (ap) ap->playSFX(sfxNum, 0x10, item->getObjId(), -1, true, PITCH_SHIFT_NONE, 0xff, true); else warning("I_playAmbientSFXCru Error: No AudioProcess"); } return 0; } uint32 AudioProcess::I_isSFXPlaying(const uint8 *args, unsigned int argsize) { ARG_SINT16(sfxNum); AudioProcess *ap = AudioProcess::get_instance(); if (ap) return ap->isSFXPlaying(sfxNum); else warning("No AudioProcess"); return 0; } uint32 AudioProcess::I_isSFXPlayingForObject(const uint8 *args, unsigned int argsize) { ARG_ITEM_FROM_PTR(item) ARG_SINT16(sfxNum); if (!item) { warning("I_isSFXPlayingForObject: Couldn't get item"); } else { AudioProcess *ap = AudioProcess::get_instance(); if (ap) return ap->isSFXPlayingForObject(sfxNum, item->getObjId()); else warning("I_isSFXPlayingForObject Error: No AudioProcess"); } return 0; } uint32 AudioProcess::I_setVolumeSFX(const uint8 *args, unsigned int /*argsize*/) { // Sets volume for last played instances of sfxNum (???) ARG_SINT16(sfxNum); ARG_UINT8(volume); AudioProcess *ap = AudioProcess::get_instance(); if (ap) ap->setVolumeSFX(sfxNum, volume); else warning("No AudioProcess"); return 0; } uint32 AudioProcess::I_setVolumeForObjectSFX(const uint8 *args, unsigned int /*argsize*/) { // Sets volume for last played instances of sfxNum on object ARG_ITEM_FROM_PTR(item); ARG_SINT16(sfxNum); ARG_UINT8(volume); if (!item) { warning("I_setVolumeForObjectSFX: Couldn't get item"); } else { AudioProcess *ap = AudioProcess::get_instance(); if (ap) ap->setVolumeForObjectSFX(item->getObjId(), sfxNum, volume); else warning("I_setVolumeForObjectSFX: No AudioProcess"); } return 0; } uint32 AudioProcess::I_stopSFX(const uint8 *args, unsigned int argsize) { ARG_SINT16(sfxNum); ObjId objId = 0; if (argsize == 4) { ARG_OBJID(objectId); objId = objectId; } AudioProcess *ap = AudioProcess::get_instance(); if (ap) ap->stopSFX(sfxNum, objId); else warning("No AudioProcess"); return 0; } uint32 AudioProcess::I_stopSFXCru(const uint8 *args, unsigned int argsize) { int16 sfxNum = -1; ARG_ITEM_FROM_PTR(item); if (!item) { warning("Invalid item in I_stopSFXCru"); return 0; } if (argsize == 6) { ARG_SINT16(sfxNumber); sfxNum = sfxNumber; } AudioProcess *ap = AudioProcess::get_instance(); if (ap) ap->stopSFX(sfxNum, item->getObjId()); else warning("No AudioProcess"); return 0; } uint32 AudioProcess::I_stopAllSFX(const uint8 * /*args*/, unsigned int /*argsize*/) { AudioProcess *ap = AudioProcess::get_instance(); // Not *exactly* the same, but close enough for this intrinsic. if (ap) ap->stopAllExceptSpeech(); else warning("No AudioProcess"); return 0; } } // End of namespace Ultima8 } // End of namespace Ultima