/* 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 "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(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(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