scummvm/engines/sherlock/sound.cpp
Torbjörn Andersson aa2b0f5b8c SHERLOCK: Workaround for bad doorbell sound in Serrated Scalpel
The sound of the doorbell at Lord Brumwell's mansion is played at 1100
Hz. That has to be an error in the game data. The other sound effects I
checked were usually 11000 Hz, and sometimes 22000 Hz. It's hard to tell
by ear which one is correct here, but 11000 Hz seems to match the 3DO
version quite well.

This bug is present in the original DOS version, though less noticeably
so. Perhaps the original audio driver clamps the sample rate to a
reasonable interval? It still gets it wrong, but not as badly?

This fixes bug #10838.
2021-12-02 20:34:35 -08:00

350 lines
10 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "sherlock/sherlock.h"
#include "sherlock/sound.h"
#include "common/config-manager.h"
#include "audio/audiostream.h"
#include "common/algorithm.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/aiff.h"
#include "audio/decoders/wave.h"
namespace Sherlock {
static const int8 creativeADPCM_ScaleMap[64] = {
0, 1, 2, 3, 4, 5, 6, 7, 0, -1, -2, -3, -4, -5, -6, -7,
1, 3, 5, 7, 9, 11, 13, 15, -1, -3, -5, -7, -9, -11, -13, -15,
2, 6, 10, 14, 18, 22, 26, 30, -2, -6, -10, -14, -18, -22, -26, -30,
4, 12, 20, 28, 36, 44, 52, 60, -4, -12, -20, -28, -36, -44, -52, -60
};
static const uint8 creativeADPCM_AdjustMap[64] = {
0, 0, 0, 0, 0, 16, 16, 16,
0, 0, 0, 0, 0, 16, 16, 16,
240, 0, 0, 0, 0, 16, 16, 16,
240, 0, 0, 0, 0, 16, 16, 16,
240, 0, 0, 0, 0, 16, 16, 16,
240, 0, 0, 0, 0, 16, 16, 16,
240, 0, 0, 0, 0, 0, 0, 0,
240, 0, 0, 0, 0, 0, 0, 0
};
/*----------------------------------------------------------------*/
Sound::Sound(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
_digitized = false;
_voices = 0;
_soundPlaying = false;
_speechPlaying = false;
_curPriority = 0;
_soundVolume = ConfMan.hasKey("sfx_volume") ? ConfMan.getInt("sfx_volume") : 255;
_soundOn = ConfMan.hasKey("mute") ? !ConfMan.getBool("mute") : true;
_speechOn = ConfMan.hasKey("speech_mute") ? !ConfMan.getBool("speech_mute") : true;
if (IS_3DO) {
// 3DO: we don't need to prepare anything for sound
return;
}
_vm->_res->addToCache("MUSIC.LIB");
if (!_vm->_interactiveFl)
_vm->_res->addToCache("TITLE.SND");
else {
_vm->_res->addToCache("MUSIC.LIB");
if (IS_ROSE_TATTOO) {
_vm->_res->addToCache("SOUND.LIB");
} else {
_vm->_res->addToCache("SND.SND");
if (!_vm->isDemo()) {
_vm->_res->addToCache("TITLE.SND");
_vm->_res->addToCache("EPILOGUE.SND");
}
}
}
}
void Sound::syncSoundSettings() {
_digitized = !ConfMan.getBool("mute");
_speechOn = !ConfMan.getBool("mute") && !ConfMan.getBool("speech_mute");
_voices = _digitized ? 1 : 0;
}
void Sound::loadSound(const Common::String &name, int priority) {
// No implementation required in ScummVM
//warning("loadSound");
}
byte Sound::decodeSample(byte sample, byte &reference, int16 &scale) {
int16 samp = sample + scale;
int16 ref = 0;
// clip bad ADPCM-4 sample
samp = CLIP<int16>(samp, 0, 63);
ref = reference + creativeADPCM_ScaleMap[samp];
if (ref > 0xff) {
reference = 0xff;
} else {
if (ref < 0x00) {
reference = 0;
} else {
reference = (uint8)(ref & 0xff);
}
}
scale = (scale + creativeADPCM_AdjustMap[samp]) & 0xff;
return reference;
}
bool Sound::playSound(const Common::String &name, WaitType waitType, int priority, const char *libraryFilename) {
// Scalpel has only a single sound handle, so it must be stopped before starting a new sound
if (IS_SERRATED_SCALPEL)
stopSound();
Common::String filename = formFilename(name);
Audio::SoundHandle &soundHandle = (IS_SERRATED_SCALPEL) ? _scalpelEffectsHandle : getFreeSoundHandle();
if (!playSoundResource(filename, libraryFilename, Audio::Mixer::kSFXSoundType, soundHandle))
error("Could not find sound resource - %s", filename.c_str());
_soundPlaying = true;
_curPriority = priority;
if (waitType == WAIT_RETURN_IMMEDIATELY) {
return true;
}
bool retval = true;
do {
_vm->_events->pollEvents();
g_system->delayMillis(10);
if ((waitType == WAIT_KBD_OR_FINISH) && _vm->_events->kbHit()) {
retval = false;
break;
}
} while (!_vm->shouldQuit() && _mixer->isSoundHandleActive(soundHandle));
_soundPlaying = false;
_mixer->stopHandle(soundHandle);
return retval;
}
Common::String Sound::formFilename(const Common::String &name) {
Common::String filename = name;
if (!filename.contains('.')) {
if (!IS_3DO) {
if (IS_SERRATED_SCALPEL) {
filename += ".SND";
} else {
filename += ".WAV";
}
} else {
// 3DO uses .aiff extension
filename += ".AIFF";
if (!filename.contains('/')) {
// if no directory was given, use the room sounds directory
filename = "rooms/sounds/" + filename;
}
}
}
return filename;
}
void Sound::playAiff(const Common::String &name, int volume, bool loop) {
Common::File *file = new Common::File();
if (!file->open(name)) {
delete file;
return;
}
Audio::AudioStream *stream;
Audio::RewindableAudioStream *audioStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
if (loop) {
Audio::AudioStream *loopingStream = Audio::makeLoopingAudioStream(audioStream, 0);
stream = loopingStream;
} else {
stream = audioStream;
}
stopAiff();
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_aiffHandle, stream, -1, volume);
}
void Sound::stopAiff() {
if (_mixer->isSoundHandleActive(_aiffHandle)) {
_mixer->stopHandle(_aiffHandle);
}
}
void Sound::playLoadedSound(int bufNum, WaitType waitType) {
if (IS_SERRATED_SCALPEL) {
if (_mixer->isSoundHandleActive(_scalpelEffectsHandle) && (_curPriority > _vm->_scene->_sounds[bufNum]._priority))
return;
stopSound();
}
playSound(_vm->_scene->_sounds[bufNum]._name, waitType, _vm->_scene->_sounds[bufNum]._priority);
}
void Sound::freeLoadedSounds() {
// As sounds are played with DisposeAfterUse::YES, stopping the sounds also
// frees them
stopSound();
}
void Sound::stopSound() {
if (IS_SERRATED_SCALPEL) {
_mixer->stopHandle(_scalpelEffectsHandle);
} else {
for (int i = 0; i < MAX_MIXER_CHANNELS; i++)
_mixer->stopHandle(_tattooEffectsHandle[i]);
}
}
void Sound::freeDigiSound() {
_soundPlaying = false;
}
Audio::SoundHandle &Sound::getFreeSoundHandle() {
for (int i = 0; i < MAX_MIXER_CHANNELS; i++) {
if (!_mixer->isSoundHandleActive(_tattooEffectsHandle[i]))
return _tattooEffectsHandle[i];
}
error("getFreeSoundHandle: No sound handle found");
}
void Sound::setVolume(int volume) {
_soundVolume = volume;
_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume);
_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume);
_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, volume);
}
void Sound::playSpeech(const Common::String &name) {
Resources &res = *_vm->_res;
Scene &scene = *_vm->_scene;
// Stop any previously playing speech
stopSpeech();
if (IS_SERRATED_SCALPEL) {
Common::String filename = formFilename(name);
if (playSoundResource(filename, Common::String(), Audio::Mixer::kSFXSoundType, _speechHandle))
_speechPlaying = true;
} else {
// Figure out which speech library to use
Common::String libraryName = Common::String::format("speech%02d.lib", scene._currentScene);
if ((!scumm_strnicmp(name.c_str(), "SLVE12S", 7)) || (!scumm_strnicmp(name.c_str(), "WATS12X", 7))
|| (!scumm_strnicmp(name.c_str(), "HOLM12X", 7)))
libraryName = "SPEECH12.LIB";
// If the speech library file doesn't even exist, then we can't play anything
Common::File f;
if (!f.exists(libraryName))
return;
// Ensure the given library is in the cache
res.addToCache(libraryName);
if (playSoundResource(name, libraryName, Audio::Mixer::kSpeechSoundType, _speechHandle))
_speechPlaying = true;
}
}
void Sound::stopSpeech() {
_mixer->stopHandle(_speechHandle);
_speechPlaying = false;
}
bool Sound::isSpeechPlaying() {
_speechPlaying = _mixer->isSoundHandleActive(_speechHandle);
return _speechPlaying;
}
bool Sound::playSoundResource(const Common::String &name, const Common::String &libFilename,
Audio::Mixer::SoundType soundType, Audio::SoundHandle &handle) {
Resources &res = *_vm->_res;
Common::SeekableReadStream *stream = libFilename.empty() ? res.load(name) : res.load(name, libFilename, true);
if (!stream)
return false;
Audio::AudioStream *audioStream;
if (IS_ROSE_TATTOO && soundType == Audio::Mixer::kSpeechSoundType) {
audioStream = Audio::makeRawStream(stream, 11025, Audio::FLAG_UNSIGNED);
} else if (IS_3DO) {
// 3DO: AIFF file
audioStream = Audio::makeAIFFStream(stream, DisposeAfterUse::YES);
} else if (IS_SERRATED_SCALPEL) {
stream->skip(2);
int size = stream->readUint32BE();
int rate = stream->readUint16BE();
byte *data = (byte *)malloc(size);
byte *ptr = data;
stream->read(ptr, size);
delete stream;
assert(size > 2);
byte *decoded = (byte *)malloc((size - 1) * 2);
// WORKAROUND: The doorbell at Lord Brumwell's mansion sounds really
// strange at 1100 Hz. The game isn't heavy on sounds, but other sound
// effects seem to be mostly at 11000 Hz, and that sounds about right
// here since it matches the 3DO version.
if (name == "JFCHIME.SND" && rate == 1100) {
rate = 11000;
}
// Holmes uses Creative ADPCM 4-bit data
int counter = 0;
byte reference = ptr[0];
int16 scale = 0;
for (int i = 1; i < size; i++) {
decoded[counter++] = decodeSample((ptr[i] >> 4) & 0x0f, reference, scale);
decoded[counter++] = decodeSample((ptr[i] >> 0) & 0x0f, reference, scale);
}
free(data);
audioStream = Audio::makeRawStream(decoded, (size - 2) * 2, rate, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
} else {
audioStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
}
if (!audioStream)
return false;
_mixer->playStream(soundType, &handle, audioStream, -1, Audio::Mixer::kMaxChannelVolume);
return true;
}
} // End of namespace Sherlock