/* 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(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); // 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