408 lines
11 KiB
C++
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
|