Files
2026-02-02 04:50:13 +01:00

408 lines
11 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/memstream.h"
#include "audio/audiostream.h"
#include "audio/decoders/adpcm.h"
#include "audio/decoders/wave.h"
#include "asylum/system/config.h"
#include "asylum/system/sound.h"
#include "asylum/resources/actor.h"
#include "asylum/resources/worldstats.h"
#include "asylum/views/scene.h"
#include "asylum/asylum.h"
#include "asylum/respack.h"
namespace Asylum {
Sound::Sound(AsylumEngine *engine, Audio::Mixer *mixer) : _vm(engine), _mixer(mixer), _musicVolume(-10000) {
}
Sound::~Sound() {
cleanupQueue();
}
//////////////////////////////////////////////////////////////////////////
// Playing sounds & music
//////////////////////////////////////////////////////////////////////////
void Sound::playSound(ResourceId resourceId, bool looping, int32 volume, int32 panning) {
debugC(kDebugLevelSound, "[Sound] Playing Sound 0x%08X", resourceId);
// Cleanup sound queue
cleanupQueue();
if (volume <= -10000)
return;
if (_vm->checkGameVersion("Demo") && RESOURCE_PACK(resourceId) == kResourcePackSound)
resourceId = MAKE_RESOURCE(kResourcePackShared, RESOURCE_INDEX(resourceId));
SoundQueueItem *item = getItem(resourceId);
if (item) {
// Duplicate the queue entry
item = addToQueue(item->resourceId);
} else {
// Check that the sound is valid
if (!isValidSoundResource(resourceId))
return;
item = addToQueue(resourceId);
}
// Original sets position back to 0
_mixer->stopHandle(item->handle);
Audio::Mixer::SoundType soundType;
switch (RESOURCE_PACK(resourceId)) {
case kResourcePackShared:
soundType = Audio::Mixer::kPlainSoundType;
break;
case kResourcePackSpeech:
case kResourcePackSharedSound:
soundType = Audio::Mixer::kSpeechSoundType;
break;
default:
soundType = Audio::Mixer::kSFXSoundType;
break;
}
ResourceEntry *resource = getResource()->get(resourceId);
playSoundData(soundType, &item->handle, resource->data, resource->size, looping, volume, panning);
}
void Sound::playMusic(ResourceId resourceId, int32 volume) {
debugC(kDebugLevelSound, "[Sound] Playing Music 0x%08X", resourceId);
if (resourceId == kResourceNone) {
stopMusic();
return;
}
// Sets the music volume
setMusicVolume(volume);
// Check if music is already playing
if (_mixer->isSoundHandleActive(_musicHandle))
stopMusic();
if (!isValidSoundResource(resourceId))
return;
ResourceEntry *resource = getResource()->get(resourceId);
playSoundData(Audio::Mixer::kMusicSoundType, &_musicHandle, resource->data, resource->size, true, volume, 0);
}
void Sound::changeMusic(int32 index, int32 musicStatusExt) {
if (index != getWorld()->musicCurrentResourceIndex) {
getWorld()->musicResourceIndex = index;
getWorld()->musicStatusExt = musicStatusExt;
getWorld()->musicFlag = 1;
}
}
bool Sound::isPlaying(ResourceId resourceId) {
return (getPlayingItem(resourceId) != nullptr);
}
//////////////////////////////////////////////////////////////////////////
// Volume & panning
//////////////////////////////////////////////////////////////////////////
void Sound::setVolume(ResourceId resourceId, int32 volume) {
SoundQueueItem *item = getPlayingItem(resourceId);
if (!item)
return;
convertVolumeFrom(volume);
_mixer->setChannelVolume(item->handle, (byte)volume);
}
void Sound::setMusicVolume(int32 volume) {
if (volume < -10000)
return;
// Save music volume (we need to be able to return it to the logic code
_musicVolume = volume;
convertVolumeFrom(volume);
_mixer->setChannelVolume(_musicHandle, (byte)volume);
}
void Sound::setPanning(ResourceId resourceId, int32 panning) {
if (Config.performance == 1)
return;
SoundQueueItem *item = getPlayingItem(resourceId);
if (!item)
return;
convertPan(panning);
_mixer->setChannelBalance(item->handle, (int8)panning);
}
int32 Sound::calculateVolumeAdjustement(const Common::Point &point, int32 attenuation, int32 delta) {
if (!attenuation)
return -(delta * delta);
Actor *player = getScene()->getActor();
Common::Point adjusted(point);
Common::Point sumPlayer = *player->getPoint1() + *player->getPoint2();
if (getSharedData()->getGlobalPoint().x == -1)
adjusted -= sumPlayer;
else
adjusted -= getSharedData()->getGlobalPoint();
int32 adjustedVolume = getAdjustedVolume(adjusted.x * adjusted.x + adjusted.y * adjusted.y);
Common::Rational invAtt(100, attenuation);
Common::Rational v;
if (invAtt.toInt())
v = Common::Rational(adjustedVolume, 1) / invAtt;
else
v = Common::Rational(delta, 1);
int32 volume = (v.toInt() - delta) * (v.toInt() - delta);
if (volume > 10000)
return -10000;
return -volume;
}
int32 Sound::getAdjustedVolume(int32 volume) const {
if (volume < 2)
return volume;
uint32 counter = (uint32)(log((double)volume) / log(2.0)) / 2;
uint32 adjustedVolume = (uint32)pow(2.0, (int32)counter);
uint32 offset = adjustedVolume;
uint32 base = adjustedVolume << counter;
for (;;) {
--counter;
if ((int32)counter < 0)
break;
offset /= 2;
uint32 val = base + ((offset + 2 * (uint32)volume) << counter);
if (val <= (uint32)volume) {
adjustedVolume += offset;
base = val;
}
}
return adjustedVolume;
}
int32 Sound::calculatePanningAtPoint(const Common::Point &point) {
// point.y does not seem to be used at all :S
int delta = point.x - getWorld()->xLeft;
if (delta < 0)
return (getWorld()->reverseStereo ? 10000 : -10000);
if (delta >= 640)
return (getWorld()->reverseStereo ? -10000 : 10000);
int sign, absDelta;
if (delta > 320) {
absDelta = delta - 320;
sign = (getWorld()->reverseStereo ? -1 : 1);
} else {
absDelta = 320 - delta;
sign = (getWorld()->reverseStereo ? 1 : -1);
}
Common::Rational v(absDelta, 6);
int32 volume = v.toInt() * v.toInt();
if (volume > 10000)
volume = 10000;
return volume * sign;
}
//////////////////////////////////////////////////////////////////////////
// Stopping sounds
//////////////////////////////////////////////////////////////////////////
void Sound::stop(ResourceId resourceId) {
SoundQueueItem *item = getPlayingItem(resourceId);
if (item != nullptr)
_mixer->stopHandle(item->handle);
}
void Sound::stopAll(ResourceId resourceId) {
for (auto &sound : _soundQueue)
if (sound.resourceId == resourceId)
_mixer->stopHandle(sound.handle);
}
void Sound::stopAll() {
for (auto &sound : _soundQueue)
_mixer->stopHandle(sound.handle);
}
void Sound::stopMusic() {
_mixer->stopHandle(_musicHandle);
}
//////////////////////////////////////////////////////////////////////////
// Helper functions
//////////////////////////////////////////////////////////////////////////
void Sound::playSoundData(Audio::Mixer::SoundType type, Audio::SoundHandle *handle, byte *soundData, uint32 soundDataLength, bool loop, int32 vol, int32 pan) {
Common::MemoryReadStream *stream = new Common::MemoryReadStream(soundData, soundDataLength);
Audio::RewindableAudioStream *sndStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
// Convert volume and panning
convertVolumeFrom(vol);
convertPan(pan);
_mixer->playStream(type, handle, Audio::makeLoopingAudioStream(sndStream, loop ? 0 : 1), -1, (byte)vol, (int8)pan);
}
//////////////////////////////////////////////////////////////////////////
// Sound buffer
//////////////////////////////////////////////////////////////////////////
SoundQueueItem *Sound::getItem(ResourceId resourceId) {
for (uint32 i = 0; i < _soundQueue.size(); i++)
if (resourceId == _soundQueue[i].resourceId)
return &_soundQueue[i];
return nullptr;
}
SoundQueueItem *Sound::getPlayingItem(ResourceId resourceId) {
for (uint32 i = 0; i < _soundQueue.size(); i++)
if (resourceId == _soundQueue[i].resourceId
&& _mixer->isSoundHandleActive(_soundQueue[i].handle))
return &_soundQueue[i];
return nullptr;
}
SoundQueueItem *Sound::addToQueue(ResourceId resourceId) {
debugC(kDebugLevelSound, "[Sound] Queueing Sound 0x%08X", resourceId);
SoundQueueItem sound;
sound.resourceId = resourceId;
_soundQueue.push_back(sound);
return &_soundQueue.back();
}
void Sound::cleanupQueue() {
for (uint i = 0; i < _soundQueue.size(); i++) {
if (_mixer->isSoundHandleActive(_soundQueue[i].handle))
continue;
// Remove the finished sound from the queue
_soundQueue.remove_at(i);
--i;
}
}
//////////////////////////////////////////////////////////////////////////
// Helper functions
//////////////////////////////////////////////////////////////////////////
bool Sound::isValidSoundResource(ResourceId resourceId) {
ResourceEntry *entry = getResource()->get(resourceId);
if (memcmp(entry->data, "RIFF", 4) != 0)
return false;
if (memcmp(&entry->data[8], "WAVE", 4) != 0)
return false;
// Original checks for "fmt " and "data" tags and return values to the calling function
return true;
}
//////////////////////////////////////////////////////////////////////////
// Conversion functions
//
// Those are from engines/agos/sound.cpp (FIXME: Move to common code?)
//////////////////////////////////////////////////////////////////////////
void Sound::convertVolumeFrom(int32 &vol) {
// DirectSound was originally used, which specifies volume
// and panning differently than ScummVM does, using a logarithmic scale
// rather than a linear one.
//
// Volume is a value between -10,000 and 0.
//
// In both cases, the -10,000 represents -100 dB. When panning, only
// one speaker's volume is affected - just like in ScummVM - with
// negative values affecting the left speaker, and positive values
// affecting the right speaker. Thus -10,000 means the left speaker is
// silent.
int32 v = CLIP<int32>(vol, -10000, 0);
if (v) {
vol = (int)((double)Audio::Mixer::kMaxChannelVolume * pow(10.0, (double)v / 2000.0) + 0.5);
} else {
vol = Audio::Mixer::kMaxChannelVolume;
}
}
void Sound::convertVolumeTo(int32 &vol) {
vol = vol ? (int32)log10((vol - 0.5) / Audio::Mixer::kMaxChannelVolume) * 2000 : -9999;
}
void Sound::convertPan(int32 &pan) {
// DirectSound was originally used, which specifies volume
// and panning differently than ScummVM does, using a logarithmic scale
// rather than a linear one.
//
// Panning is a value between -10,000 and 10,000.
//
// In both cases, the -10,000 represents -100 dB. When panning, only
// one speaker's volume is affected - just like in ScummVM - with
// negative values affecting the left speaker, and positive values
// affecting the right speaker. Thus -10,000 means the left speaker is
// silent.
int32 p = CLIP<int32>(pan, -10000, 10000);
if (p < 0) {
pan = 129 * (1 - pow(10.0, p / 5000.0));
} else {
pan = -129 * (1 - pow(10.0, p / -5000.0));
}
}
} // end of namespace Asylum