mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 13:50:13 +00:00
351 lines
10 KiB
C++
351 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 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 "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::Path &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::Path &name, WaitType waitType, int priority, const Common::Path &libraryFilename) {
|
|
// Scalpel has only a single sound handle, so it must be stopped before starting a new sound
|
|
if (IS_SERRATED_SCALPEL)
|
|
stopSound();
|
|
|
|
Common::Path 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.toString().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::Path Sound::formFilename(const Common::Path &name) {
|
|
Common::Path filename = name;
|
|
|
|
if (!filename.baseName().contains('.')) {
|
|
if (!IS_3DO) {
|
|
if (IS_SERRATED_SCALPEL) {
|
|
filename.appendInPlace(".SND");
|
|
} else {
|
|
filename.appendInPlace(".WAV");
|
|
}
|
|
} else {
|
|
// 3DO uses .aiff extension
|
|
filename.appendInPlace(".AIFF");
|
|
if (filename.getParent().empty()) {
|
|
// if no directory was given, use the room sounds directory
|
|
filename = Common::Path("rooms/sounds/").appendInPlace(filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
void Sound::playAiff(const Common::Path &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(Common::Path(_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::Path &name) {
|
|
Resources &res = *_vm->_res;
|
|
Scene &scene = *_vm->_scene;
|
|
|
|
// Stop any previously playing speech
|
|
stopSpeech();
|
|
|
|
if (IS_SERRATED_SCALPEL) {
|
|
Common::Path filename = formFilename(name);
|
|
if (playSoundResource(filename, Common::Path(), Audio::Mixer::kSFXSoundType, _speechHandle))
|
|
_speechPlaying = true;
|
|
} else {
|
|
// Figure out which speech library to use
|
|
Common::Path libraryName(Common::String::format("speech%02d.lib", scene._currentScene));
|
|
Common::String baseName(name.baseName());
|
|
if ((!scumm_strnicmp(baseName.c_str(), "SLVE12S", 7)) || (!scumm_strnicmp(baseName.c_str(), "WATS12X", 7))
|
|
|| (!scumm_strnicmp(baseName.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::Path &name, const Common::Path &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 and fog horn 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" || name == "JFFOG.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
|
|
|