[Scummvm-git-logs] scummvm master -> 85f1448f436a84611b63ef2b33b1c14ce11df301
elasota
noreply at scummvm.org
Sun Mar 12 02:57:48 UTC 2023
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
85f1448f43 VCRUISE: Add 3D sound support
Commit: 85f1448f436a84611b63ef2b33b1c14ce11df301
https://github.com/scummvm/scummvm/commit/85f1448f436a84611b63ef2b33b1c14ce11df301
Author: elasota (ejlasota at gmail.com)
Date: 2023-03-11T21:57:15-05:00
Commit Message:
VCRUISE: Add 3D sound support
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 713728c5fc6..3b02263e27b 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -20,7 +20,9 @@
*/
#include "common/formats/winexe.h"
+#include "common/endian.h"
#include "common/file.h"
+#include "common/math.h"
#include "common/ptr.h"
#include "common/random.h"
#include "common/system.h"
@@ -282,7 +284,9 @@ void SfxData::load(Common::SeekableReadStream &stream, Audio::Mixer *mixer) {
}
}
-CachedSound::CachedSound() : rampStartVolume(0), rampEndVolume(0), rampRatePerMSec(0), rampStartTime(0), rampTerminateOnCompletion(false), volume(0), balance(0) {
+CachedSound::CachedSound()
+ : rampStartVolume(0), rampEndVolume(0), rampRatePerMSec(0), rampStartTime(0), rampTerminateOnCompletion(false),
+ volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), x(0), y(0), z(0) {
}
CachedSound::~CachedSound() {
@@ -312,6 +316,15 @@ StaticAnimParams::StaticAnimParams() : initialDelay(0), repeatDelay(0), lockInte
StaticAnimation::StaticAnimation() : currentAlternation(0), nextStartTime(0) {
}
+FrameData::FrameData() : areaID{0, 0, 0, 0}, areaFrameIndex(0), frameIndex(0), frameType(0), roomNumber(0) {
+}
+
+FrameData2::FrameData2() : x(0), y(0), angle(0), frameNumberInArea(0), unknown(0) {
+}
+
+SoundParams3D::SoundParams3D() : minRange(0), maxRange(0), unknownRange(0) {
+}
+
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _havePanAnimations(0), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingReturnToIdleState(false), _havePendingCompletionCheck(false),
@@ -319,8 +332,9 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_loadedAnimation(0), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0),
_animFrameRateLock(0), _animStartTime(0), _animFramesDecoded(0), _animDecoderState(kAnimDecoderStateStopped),
_animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleHaveClickInteraction(false), _idleHaveDragInteraction(false), _idleInteractionID(0),
- _lmbDown(false), _lmbDragging(false), _lmbReleaseWasClick(false), _lmbDownTime(0),
- _panoramaState(kPanoramaStateInactive) {
+ _loadedArea(0), _lmbDown(false), _lmbDragging(false), _lmbReleaseWasClick(false), _lmbDownTime(0),
+ _panoramaState(kPanoramaStateInactive),
+ _listenerX(0), _listenerY(0), _listenerAngle(0) {
for (uint i = 0; i < kNumDirections; i++) {
_haveIdleAnimations[i] = false;
@@ -872,6 +886,16 @@ void Runtime::continuePlayingAnimation(bool loop, bool useStopFrame, bool &outAn
_animPendingDecodeFrame++;
_animFramesDecoded++;
+ if (_animDisplayingFrame < _frameData2.size()) {
+ const FrameData2 &fd2 = _frameData2[_animDisplayingFrame];
+
+ _listenerX = fd2.x;
+ _listenerY = fd2.y;
+ _listenerAngle = fd2.angle;
+ }
+
+ update3DSounds();
+
if (_animPlaylist) {
uint decodeFrameInPlaylist = _animDisplayingFrame - _animFirstFrame;
for (const SfxPlaylistEntry &playlistEntry : _animPlaylist->entries) {
@@ -1546,6 +1570,75 @@ void Runtime::loadMap(Common::SeekableReadStream *stream) {
}
}
+void Runtime::loadFrameData(Common::SeekableReadStream *stream) {
+ int64 size = stream->size();
+ if (size < 2048 || size > 0xffffffu)
+ error("Unexpected DTA size");
+
+ uint numFrameDatas = (static_cast<uint>(size) - 2048u) / 16;
+
+ if (!stream->seek(2048))
+ error("Error skipping DTA header");
+
+ _frameData.resize(numFrameDatas);
+
+ for (uint i = 0; i < numFrameDatas; i++) {
+ byte frameData[16];
+
+ if (stream->read(frameData, 16) != 16)
+ error("Error reading DTA frame data");
+
+ FrameData &fd = _frameData[i];
+ fd.frameType = frameData[0];
+ fd.frameIndex = frameData[1] | (frameData[2] << 8) | (frameData[3] << 16);
+ fd.roomNumber = static_cast<int8>(frameData[4]);
+ memcpy(fd.areaID, frameData + 8, 4);
+
+ char decAreaFrameIndex[4];
+ memcpy(decAreaFrameIndex, frameData + 12, 4);
+
+ uint areaFrameIndex = 0;
+ for (int digit = 0; digit < 4; digit++) {
+ char c = decAreaFrameIndex[digit];
+ if (c < '0' || c > '9')
+ error("Invalid area frame index in DTA data");
+
+ areaFrameIndex = areaFrameIndex * 10u + static_cast<uint>(c - '0');
+ }
+
+ fd.areaFrameIndex = areaFrameIndex;
+
+ if (i != fd.frameIndex)
+ error("DTA frame index was out-of-line, don't know how to handle this");
+ }
+}
+
+void Runtime::loadFrameData2(Common::SeekableReadStream *stream) {
+ int64 size = stream->size();
+ if (size > 0xffffffu)
+ error("Unexpected 2DT size");
+
+ uint numFrameDatas = static_cast<uint>(size) / 16;
+
+ if (numFrameDatas == 0)
+ return;
+
+ _frameData2.resize(numFrameDatas);
+
+ uint32 numBytesToRead = numFrameDatas * 16;
+ if (stream->read(&_frameData2[0], numBytesToRead) != numBytesToRead)
+ error("Failed to read 2DT data");
+
+ for (uint i = 0; i < numFrameDatas; i++) {
+ FrameData2 &fd2 = _frameData2[i];
+ fd2.x = READ_LE_INT32(&fd2.x);
+ fd2.y = READ_LE_INT32(&fd2.y);
+ fd2.angle = READ_LE_INT32(&fd2.angle);
+ fd2.frameNumberInArea = READ_LE_UINT16(&fd2.frameNumberInArea);
+ fd2.unknown = READ_LE_UINT16(&fd2.unknown);
+ }
+}
+
void Runtime::changeMusicTrack(int track) {
_musicPlayer.reset();
@@ -1579,6 +1672,8 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
if (_loadedAnimation != static_cast<uint>(animFile)) {
_loadedAnimation = animFile;
+ _frameData.clear();
+ _frameData2.clear();
_animDecoder.reset();
_animDecoderState = kAnimDecoderStateStopped;
@@ -1603,6 +1698,21 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
if (sfxFile.open(sfxFileName))
_sfxData.load(sfxFile, _mixer);
+ sfxFile.close();
+
+ Common::String dtaFileName = Common::String::format("Anims/Anim%04i.dta", animFile);
+ Common::File dtaFile;
+
+ if (dtaFile.open(dtaFileName))
+ loadFrameData(&dtaFile);
+ dtaFile.close();
+
+ Common::String twoDtFileName = Common::String::format("Dta/Anim%04i.2dt", animFile);
+ Common::File twoDtFile;
+
+ if (twoDtFile.open(twoDtFileName))
+ loadFrameData2(&twoDtFile);
+ twoDtFile.close();
}
if (_animDecoderState == kAnimDecoderStatePlaying) {
@@ -1634,7 +1744,20 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
debug(1, "Animation last frame set to %u", animDef.lastFrame);
}
-void Runtime::triggerSound(bool looping, uint soundID, uint volume, int32 balance) {
+void Runtime::setSound3DParameters(uint soundID, int32 x, int32 y, const SoundParams3D &soundParams3D) {
+ Common::HashMap<uint, Common::SharedPtr<CachedSound> >::iterator it = _cachedSounds.find(soundID);
+ if (it == _cachedSounds.end()) {
+ warning("Couldn't set sound parameters for sound ID %u, the sound wasn't loaded", soundID);
+ return;
+ }
+
+ CachedSound &snd = *it->_value;
+ snd.x = x;
+ snd.y = y;
+ snd.params3D = soundParams3D;
+}
+
+void Runtime::triggerSound(bool looping, uint soundID, uint volume, int32 balance, bool is3D) {
Common::HashMap<uint, Common::SharedPtr<CachedSound> >::iterator it = _cachedSounds.find(soundID);
if (it == _cachedSounds.end()) {
warning("Couldn't trigger sound ID %u, the sound wasn't loaded", soundID);
@@ -1645,6 +1768,9 @@ void Runtime::triggerSound(bool looping, uint soundID, uint volume, int32 balanc
snd.volume = volume;
snd.balance = balance;
+ snd.is3D = is3D;
+
+ computeEffectiveVolumeAndBalance(snd);
// Reset if looping state changes
if (snd.loopingStream && !looping) {
@@ -1665,15 +1791,15 @@ void Runtime::triggerSound(bool looping, uint soundID, uint volume, int32 balanc
if (!looping) {
snd.player->stop();
snd.stream->rewind();
- snd.player->play(volume, balance);
+ snd.player->play(snd.effectiveVolume, snd.effectiveBalance);
} else {
// Adjust volume and balance at least
- snd.player->setVolume(volume);
- snd.player->setBalance(balance);
+ snd.player->setVolume(snd.effectiveVolume);
+ snd.player->setBalance(snd.effectiveBalance);
}
} else {
snd.player.reset(new AudioPlayer(_mixer, looping ? snd.loopingStream.staticCast<Audio::AudioStream>() : snd.stream.staticCast<Audio::AudioStream>()));
- snd.player->play(volume, balance);
+ snd.player->play(snd.effectiveVolume, snd.effectiveBalance);
}
}
@@ -1725,6 +1851,90 @@ void Runtime::updateSounds(uint32 timestamp) {
_cachedSounds.erase(id);
}
+void Runtime::update3DSounds() {
+ for (const Common::HashMap<uint, Common::SharedPtr<CachedSound> >::Node &node : _cachedSounds) {
+ CachedSound &snd = *node._value;
+
+ if (!snd.is3D)
+ continue;
+
+ bool changed = computeEffectiveVolumeAndBalance(snd);
+
+ if (changed) {
+ VCruise::AudioPlayer *player = snd.player.get();
+ if (player) {
+ player->setVolume(snd.effectiveVolume);
+ player->setBalance(snd.effectiveBalance);
+ }
+ }
+ }
+}
+
+bool Runtime::computeEffectiveVolumeAndBalance(CachedSound &snd) {
+ uint effectiveVolume = snd.volume;
+ int32 effectiveBalance = snd.balance;
+
+ double radians = Common::deg2rad<double>(_listenerAngle);
+ int32 cosAngle = static_cast<int32>(cos(radians) * (1 << 15));
+ int32 sinAngle = static_cast<int32>(sin(radians) * (1 << 15));
+
+ if (snd.is3D) {
+ int32 dx = snd.x - _listenerX;
+ int32 dy = snd.y - _listenerY;
+
+ uint distance = static_cast<uint>(sqrt(dx * dx + dy * dy));
+
+ if (distance >= snd.params3D.maxRange)
+ effectiveVolume = 0;
+ else if (distance > snd.params3D.minRange) {
+ uint rangeDelta = snd.params3D.maxRange - snd.params3D.minRange;
+
+ effectiveVolume = (snd.params3D.maxRange - distance) * effectiveVolume / rangeDelta;
+ }
+
+ int32 dxNormalized = 0;
+ int32 dyNormalized = 0;
+ if (distance > 0) {
+ dxNormalized = dx * (1 << 10) / static_cast<int32>(distance);
+ dyNormalized = dy * (1 << 10) / static_cast<int32>(distance);
+ }
+
+ int32 balance16 = (sinAngle * dxNormalized - cosAngle * dyNormalized) >> 9;
+
+ // Reduce to 3/5 intensity. This means that at full balance, the opposing volume will be 1/4 the facing volume.
+ balance16 = (balance16 * 9830 + (1 << 13)) >> 14;
+
+ if (balance16 > 65536)
+ balance16 = 65536;
+ else if (balance16 < -65536)
+ balance16 = -65536;
+
+ uint rightVolume = ((65536u + balance16) * effectiveVolume) >> 16;
+ uint leftVolume = ((65536u - balance16) * effectiveVolume) >> 16;
+
+ if (leftVolume == 0 && rightVolume == 0) {
+ // This should never happen
+ effectiveVolume = 0;
+ effectiveBalance = 0;
+ } else {
+ if (leftVolume <= rightVolume) {
+ effectiveVolume = rightVolume;
+ effectiveBalance = 127 - (leftVolume * 127 / rightVolume);
+ } else {
+ effectiveVolume = leftVolume;
+ effectiveBalance = (rightVolume * 127 / leftVolume) - 127;
+ }
+ }
+ }
+
+ bool changed = (effectiveVolume != snd.effectiveVolume || effectiveBalance != snd.effectiveBalance);
+
+ snd.effectiveVolume = effectiveVolume;
+ snd.effectiveBalance = effectiveBalance;
+
+ return changed;
+}
+
AnimationDef Runtime::stackArgsToAnimDef(const StackValue_t *args) const {
AnimationDef def;
def.animNum = args[0];
@@ -2453,44 +2663,44 @@ void Runtime::scriptOpLMB1(ScriptArg_t arg) {
void Runtime::scriptOpSoundS1(ScriptArg_t arg) {
TAKE_STACK(1);
- triggerSound(false, stackArgs[0], 100, 0);
+ triggerSound(false, stackArgs[0], 100, 0, false);
}
void Runtime::scriptOpSoundS2(ScriptArg_t arg) {
TAKE_STACK(2);
- triggerSound(false, stackArgs[0], stackArgs[1], 0);
+ triggerSound(false, stackArgs[0], stackArgs[1], 0, false);
}
void Runtime::scriptOpSoundS3(ScriptArg_t arg) {
TAKE_STACK(3);
- triggerSound(false, stackArgs[0], stackArgs[1], stackArgs[2]);
+ triggerSound(false, stackArgs[0], stackArgs[1], stackArgs[2], false);
}
void Runtime::scriptOpSoundL1(ScriptArg_t arg) {
TAKE_STACK(1);
- triggerSound(true, stackArgs[0], 100, 0);
+ triggerSound(true, stackArgs[0], 100, 0, false);
}
void Runtime::scriptOpSoundL2(ScriptArg_t arg) {
TAKE_STACK(2);
- triggerSound(true, stackArgs[0], stackArgs[1], 0);
+ triggerSound(true, stackArgs[0], stackArgs[1], 0, false);
}
void Runtime::scriptOpSoundL3(ScriptArg_t arg) {
TAKE_STACK(3);
- triggerSound(true, stackArgs[0], stackArgs[1], stackArgs[2]);
+ triggerSound(true, stackArgs[0], stackArgs[1], stackArgs[2], false);
}
void Runtime::scriptOp3DSoundL2(ScriptArg_t arg) {
TAKE_STACK(4);
- warning("3D sound loop not implemented yet");
- (void)stackArgs;
+ setSound3DParameters(stackArgs[0], stackArgs[2], stackArgs[3], _pendingSoundParams3D);
+ triggerSound(true, stackArgs[0], stackArgs[1], 0, true);
}
void Runtime::scriptOpAddXSound(ScriptArg_t arg) {
@@ -2518,8 +2728,9 @@ void Runtime::scriptOpStopSndLO(ScriptArg_t arg) {
void Runtime::scriptOpRange(ScriptArg_t arg) {
TAKE_STACK(3);
- warning("Range not implemented yet");
- (void)stackArgs;
+ _pendingSoundParams3D.minRange = stackArgs[0];
+ _pendingSoundParams3D.maxRange = stackArgs[1];
+ _pendingSoundParams3D.unknownRange = stackArgs[2];
}
void Runtime::scriptOpMusic(ScriptArg_t arg) {
@@ -2686,7 +2897,7 @@ void Runtime::scriptOpSay3(ScriptArg_t arg) {
error("Invalid interrupt arg for say3, only 1 is supported.");
if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
- triggerSound(false, oneShot.soundID, 100, 0);
+ triggerSound(false, oneShot.soundID, 100, 0, false);
_triggeredOneShots.push_back(oneShot);
}
}
@@ -2703,7 +2914,7 @@ void Runtime::scriptOpSay3Get(ScriptArg_t arg) {
error("Invalid interrupt arg for say3, only 1 is supported.");
if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
- triggerSound(false, oneShot.soundID, 100, 0);
+ triggerSound(false, oneShot.soundID, 100, 0, false);
_triggeredOneShots.push_back(oneShot);
_scriptStack.push_back(0);
} else
@@ -2947,8 +3158,6 @@ void Runtime::scriptOpVarName(ScriptArg_t arg) {
void Runtime::scriptOpSoundName(ScriptArg_t arg) {
Common::String sndName = _scriptSet->strings[arg];
- warning("Sound IDs are not implemented yet");
-
uint soundID = 0;
for (uint i = 0; i < 4; i++)
soundID = soundID * 10u + (sndName[i] - '0');
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index c69c7c548d4..74a2634761b 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -62,6 +62,8 @@ class AVIDecoder;
namespace VCruise {
static const uint kNumDirections = 8;
+static const uint kNumHighPrecisionDirections = 256;
+static const uint kHighPrecisionDirectionMultiplier = kNumHighPrecisionDirections / kNumDirections;
class AudioPlayer;
class TextParser;
@@ -171,6 +173,17 @@ struct SfxData {
SoundMap_t sounds;
};
+struct SoundParams3D {
+ SoundParams3D();
+
+ uint minRange;
+ uint maxRange;
+
+ // Not sure what this does. It's always shorter than the min range but after many tests, I've been
+ // unable to detect any level changes from altering this parameter.
+ uint unknownRange;
+};
+
struct CachedSound {
CachedSound();
~CachedSound();
@@ -188,6 +201,16 @@ struct CachedSound {
uint volume;
int32 balance;
+
+ uint effectiveVolume;
+ int32 effectiveBalance;
+
+ bool is3D;
+ int32 x;
+ int32 y;
+ int32 z;
+
+ SoundParams3D params3D;
};
struct TriggeredOneShot {
@@ -218,6 +241,26 @@ struct StaticAnimation {
uint currentAlternation;
};
+struct FrameData {
+ FrameData();
+
+ uint32 frameIndex;
+ uint16 areaFrameIndex;
+ int8 roomNumber;
+ uint8 frameType; // 0x01 = Keyframe, 0x02 = Intra frame (not used in Schizm), 0x41 = Last frame
+ char areaID[4];
+};
+
+struct FrameData2 {
+ FrameData2();
+
+ int32 x;
+ int32 y;
+ int32 angle;
+ uint16 frameNumberInArea;
+ uint16 unknown; // Subarea or something?
+};
+
class Runtime {
public:
Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID);
@@ -396,14 +439,19 @@ private:
bool dischargeIdleMouseDown();
bool dischargeIdleClick();
void loadMap(Common::SeekableReadStream *stream);
+ void loadFrameData(Common::SeekableReadStream *stream);
+ void loadFrameData2(Common::SeekableReadStream *stream);
void changeMusicTrack(int musicID);
void changeAnimation(const AnimationDef &animDef, bool consumeFPSOverride);
void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride);
- void triggerSound(bool looping, uint soundID, uint volume, int32 balance);
+ void setSound3DParameters(uint soundID, int32 x, int32 y, const SoundParams3D &soundParams3D);
+ void triggerSound(bool looping, uint soundID, uint volume, int32 balance, bool is3D);
void triggerSoundRamp(uint soundID, uint durationMSec, uint newVolume, bool terminateOnCompletion);
void updateSounds(uint32 timestamp);
+ void update3DSounds();
+ bool computeEffectiveVolumeAndBalance(CachedSound &snd);
AnimationDef stackArgsToAnimDef(const StackValue_t *args) const;
void pushAnimDef(const AnimationDef &animDef);
@@ -422,7 +470,7 @@ private:
void panoramaActivate();
bool computeFaceDirectionAnimation(uint desiredDirection, const AnimationDef *&outAnimDef, uint &outInitialFrame, uint &outStopFrame);
-
+
// Script things
void scriptOpNumber(ScriptArg_t arg);
void scriptOpRotate(ScriptArg_t arg);
@@ -537,6 +585,7 @@ private:
uint _roomNumber; // Room number can be changed independently of the loaded room, the screen doesn't change until a command changes it
uint _screenNumber;
uint _direction;
+ uint _highPrecisionDirection;
GyroState _gyros;
@@ -609,6 +658,10 @@ private:
uint _loadedAnimation;
bool _animPlayWhileIdle;
+ Common::Array<FrameData> _frameData;
+ Common::Array<FrameData2> _frameData2;
+ uint32 _loadedArea;
+
Common::Array<Common::String> _animDefNames;
Common::HashMap<Common::String, uint> _animDefNameToIndex;
@@ -644,9 +697,14 @@ private:
Common::HashMap<Common::String, Common::ArchiveMemberPtr> _waves;
Common::HashMap<uint, Common::SharedPtr<CachedSound> > _cachedSounds;
+ SoundParams3D _pendingSoundParams3D;
Common::Array<TriggeredOneShot> _triggeredOneShots;
+ int32 _listenerX;
+ int32 _listenerY;
+ int32 _listenerAngle;
+
static const uint kAnimDefStackArgs = 8;
static const uint kCursorArrow = 0;
More information about the Scummvm-git-logs
mailing list