aboutsummaryrefslogtreecommitdiff
path: root/engines/neverhood/sound.cpp
diff options
context:
space:
mode:
authorjohndoe1232012-09-22 00:55:40 +0000
committerWillem Jan Palenstijn2013-05-08 20:43:42 +0200
commitfc0e40db304aa489d4117299fcce1f80ba0b6379 (patch)
treee8f25ec91d06c4150e17a661c2285c1f411b89eb /engines/neverhood/sound.cpp
parent0bb70c39f084dc122d213b07b1ef2d946608fe88 (diff)
downloadscummvm-rg350-fc0e40db304aa489d4117299fcce1f80ba0b6379.tar.gz
scummvm-rg350-fc0e40db304aa489d4117299fcce1f80ba0b6379.tar.bz2
scummvm-rg350-fc0e40db304aa489d4117299fcce1f80ba0b6379.zip
NEVERHOOD: Implement the actual audio code
Sounds and music play now in some scenes (I didn't change the comments to code in all modules yet) - Fix calcHash to ignore non-alphanumeric characters, this fixes at least one animation glitch (when inserting tapes into the player) - Move SoundResource to sound.cpp
Diffstat (limited to 'engines/neverhood/sound.cpp')
-rw-r--r--engines/neverhood/sound.cpp463
1 files changed, 421 insertions, 42 deletions
diff --git a/engines/neverhood/sound.cpp b/engines/neverhood/sound.cpp
index 91a23bfc7b..bd2f223998 100644
--- a/engines/neverhood/sound.cpp
+++ b/engines/neverhood/sound.cpp
@@ -20,36 +20,101 @@
*
*/
+#include "common/memstream.h"
#include "graphics/palette.h"
#include "neverhood/sound.h"
+#include "neverhood/resourceman.h"
namespace Neverhood {
// TODO Put more stuff into the constructors/destructors of the item structs
// TODO Some parts are quite bad here, but my priority is to get sound working at all
+SoundResource::SoundResource(NeverhoodEngine *vm)
+ : _vm(vm), _soundIndex(-1) {
+}
+
+SoundResource::~SoundResource() {
+ unload();
+}
+
+bool SoundResource::isPlaying() {
+ return _soundIndex >= 0 &&
+ _vm->_audioResourceMan->isSoundPlaying(_soundIndex);
+}
+
+void SoundResource::load(uint32 fileHash) {
+ unload();
+ _soundIndex = _vm->_audioResourceMan->addSound(fileHash);
+ _vm->_audioResourceMan->loadSound(_soundIndex);
+}
+
+void SoundResource::unload() {
+ if (_soundIndex >= 0) {
+ _vm->_audioResourceMan->removeSound(_soundIndex);
+ _soundIndex = -1;
+ }
+}
+
+void SoundResource::play(uint32 fileHash) {
+ load(fileHash);
+ play();
+}
+
+void SoundResource::play() {
+ if (_soundIndex >= 0)
+ _vm->_audioResourceMan->playSound(_soundIndex, false);
+}
+
+void SoundResource::stop() {
+ if (_soundIndex >= 0)
+ _vm->_audioResourceMan->stopSound(_soundIndex);
+}
+
+void SoundResource::setVolume(int16 volume) {
+ if (_soundIndex >= 0)
+ _vm->_audioResourceMan->setSoundVolume(_soundIndex, volume);
+}
+
+void SoundResource::setPan(int16 pan) {
+ if (_soundIndex >= 0)
+ _vm->_audioResourceMan->setSoundPan(_soundIndex, pan);
+}
+
MusicResource::MusicResource(NeverhoodEngine *vm)
- : _vm(vm) {
+ : _vm(vm), _musicIndex(-1) {
}
bool MusicResource::isPlaying() {
- return false;
+ return _musicIndex >= 0 &&
+ _vm->_audioResourceMan->isMusicPlaying(_musicIndex);
}
void MusicResource::load(uint32 fileHash) {
- // TODO
+ unload();
+ _musicIndex = _vm->_audioResourceMan->loadMusic(fileHash);
}
void MusicResource::unload() {
- // TODO
+ if (_musicIndex >= 0) {
+ _vm->_audioResourceMan->unloadMusic(_musicIndex);
+ _musicIndex = -1;
+ }
}
void MusicResource::play(int16 fadeVolumeStep) {
- // TODO
+ if (_musicIndex >= 0)
+ _vm->_audioResourceMan->playMusic(_musicIndex, fadeVolumeStep);
}
void MusicResource::stop(int16 fadeVolumeStep) {
- // TODO
+ if (_musicIndex >= 0)
+ _vm->_audioResourceMan->stopMusic(_musicIndex, fadeVolumeStep);
+}
+
+void MusicResource::setVolume(int16 volume) {
+ if (_musicIndex >= 0)
+ _vm->_audioResourceMan->setMusicVolume(_musicIndex, volume);
}
MusicItem::MusicItem()
@@ -80,6 +145,8 @@ SoundItem::~SoundItem() {
delete _soundResource;
}
+// SoundMan
+
SoundMan::SoundMan(NeverhoodEngine *vm)
: _vm(vm),
_soundIndex1(-1), _soundIndex2(-1), _soundIndex3(-1) {
@@ -107,8 +174,8 @@ void SoundMan::deleteMusic(uint32 musicFileHash) {
if (musicItem) {
delete musicItem;
for (uint i = 0; i < _musicItems.size(); ++i)
- if (_musicItems[i]->_musicFileHash == musicFileHash) {
- _musicItems.remove_at(i);
+ if (_musicItems[i] == musicItem) {
+ _musicItems[i] = NULL;
break;
}
}
@@ -149,8 +216,8 @@ void SoundMan::deleteSound(uint32 soundFileHash) {
if (soundItem) {
delete soundItem;
for (uint i = 0; i < _soundItems.size(); ++i)
- if (_soundItems[i]->_soundFileHash == soundFileHash) {
- _soundItems.remove_at(i);
+ if (_soundItems[i] == soundItem) {
+ _soundItems[i] = NULL;
break;
}
}
@@ -213,35 +280,41 @@ void SoundMan::update() {
for (uint i = 0; i < _soundItems.size(); ++i) {
SoundItem *soundItem = _soundItems[i];
- if (soundItem->_playOnceAfterCountdown) {
- if (soundItem->_currCountdown == 0) {
- soundItem->_currCountdown = soundItem->_initialCountdown;
- } else if (--soundItem->_currCountdown == 0) {
- soundItem->_soundResource->play();
+ if (soundItem) {
+ if (soundItem->_playOnceAfterCountdown) {
+ if (soundItem->_currCountdown == 0) {
+ soundItem->_currCountdown = soundItem->_initialCountdown;
+ } else if (--soundItem->_currCountdown == 0) {
+ soundItem->_soundResource->play();
+ }
+ } else if (soundItem->_playOnceAfterRandomCountdown) {
+ if (soundItem->_currCountdown == 0) {
+ if (soundItem->_minCountdown > 0 && soundItem->_maxCountdown > 0 && soundItem->_minCountdown < soundItem->_maxCountdown)
+ soundItem->_currCountdown = _vm->_rnd->getRandomNumberRng(soundItem->_minCountdown, soundItem->_maxCountdown);
+ } else if (--soundItem->_currCountdown == 0) {
+ soundItem->_soundResource->play();
+ }
+ } else if (soundItem->_playLooping && !soundItem->_soundResource->isPlaying()) {
+ soundItem->_soundResource->play(); // TODO Looping parameter?
}
- } else if (soundItem->_playOnceAfterRandomCountdown) {
- if (soundItem->_currCountdown == 0) {
- if (soundItem->_minCountdown > 0 && soundItem->_maxCountdown > 0 && soundItem->_minCountdown < soundItem->_maxCountdown)
- soundItem->_currCountdown = _vm->_rnd->getRandomNumberRng(soundItem->_minCountdown, soundItem->_maxCountdown);
- } else if (--soundItem->_currCountdown == 0) {
- soundItem->_soundResource->play();
- }
- } else if (soundItem->_playLooping && !soundItem->_soundResource->isPlaying()) {
- soundItem->_soundResource->play(); // TODO Looping parameter?
}
}
for (uint i = 0; i < _musicItems.size(); ++i) {
MusicItem *musicItem = _musicItems[i];
- if (musicItem->_countdown) {
- --musicItem->_countdown;
- } else if (musicItem->_play && !musicItem->_musicResource->isPlaying()) {
- musicItem->_musicResource->play(musicItem->_fadeVolumeStep);
- musicItem->_fadeVolumeStep = 0;
- } else if (musicItem->_stop) {
- musicItem->_musicResource->stop(musicItem->_fadeVolumeStep);
- musicItem->_fadeVolumeStep = 0;
- musicItem->_stop = false;
+ if (musicItem) {
+ if (musicItem->_countdown) {
+ --musicItem->_countdown;
+ } else if (musicItem->_play && !musicItem->_musicResource->isPlaying()) {
+ debug("SoundMan: play music %08X (fade %d)", musicItem->_musicFileHash, musicItem->_fadeVolumeStep);
+ musicItem->_musicResource->play(musicItem->_fadeVolumeStep);
+ musicItem->_fadeVolumeStep = 0;
+ } else if (musicItem->_stop) {
+ debug("SoundMan: stop music %08X (fade %d)", musicItem->_musicFileHash, musicItem->_fadeVolumeStep);
+ musicItem->_musicResource->stop(musicItem->_fadeVolumeStep);
+ musicItem->_fadeVolumeStep = 0;
+ musicItem->_stop = false;
+ }
}
}
@@ -253,11 +326,11 @@ void SoundMan::deleteGroup(uint32 nameHash) {
}
void SoundMan::deleteMusicGroup(uint32 nameHash) {
- for (int index = _musicItems.size() - 1; index >= 0; --index) {
+ for (uint index = 0; index < _musicItems.size(); ++index) {
MusicItem *musicItem = _musicItems[index];
- if (musicItem->_nameHash == nameHash) {
+ if (musicItem && musicItem->_nameHash == nameHash) {
delete musicItem;
- _musicItems.remove_at(index);
+ _musicItems[index] = NULL;
}
}
}
@@ -276,11 +349,11 @@ void SoundMan::deleteSoundGroup(uint32 nameHash) {
_soundIndex2 = -1;
}
- for (int index = _soundItems.size() - 1; index >= 0; --index) {
+ for (uint index = 0; index < _soundItems.size(); ++index) {
soundItem = _soundItems[index];
- if (soundItem->_nameHash == nameHash) {
+ if (soundItem && soundItem->_nameHash == nameHash) {
delete soundItem;
- _soundItems.remove_at(index);
+ _soundItems[index] = NULL;
}
}
@@ -359,21 +432,327 @@ void SoundMan::setSoundThreePlayFlag(bool playOnceAfterCountdown) {
MusicItem *SoundMan::getMusicItemByHash(uint32 musicFileHash) {
for (uint i = 0; i < _musicItems.size(); ++i)
- if (_musicItems[i]->_musicFileHash == musicFileHash)
+ if (_musicItems[i] && _musicItems[i]->_musicFileHash == musicFileHash)
return _musicItems[i];
return NULL;
}
SoundItem *SoundMan::getSoundItemByHash(uint32 soundFileHash) {
for (uint i = 0; i < _soundItems.size(); ++i)
- if (_soundItems[i]->_soundFileHash == soundFileHash)
+ if (_soundItems[i] && _soundItems[i]->_soundFileHash == soundFileHash)
return _soundItems[i];
return NULL;
}
+int16 SoundMan::addMusicItem(MusicItem *musicItem) {
+ return 0; // TODO
+}
+
+int16 SoundMan::addSoundItem(SoundItem *soundItem) {
+ for (uint i = 0; i < _soundItems.size(); ++i)
+ if (!_soundItems[i]) {
+ _soundItems[i] = soundItem;
+ return i;
+ }
+ int16 soundIndex = _soundItems.size();
+ _soundItems.push_back(soundItem);
+ return soundIndex;
+}
+
void SoundMan::deleteSoundByIndex(int index) {
delete _soundItems[index];
- _soundItems.remove_at(index);
+ _soundItems[index] = NULL;
+}
+
+// NeverhoodAudioStream
+
+NeverhoodAudioStream::NeverhoodAudioStream(int rate, byte shiftValue, bool isLooping, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream)
+ : _rate(rate), _shiftValue(shiftValue), _isLooping(isLooping), _isStereo(false), _stream(stream, disposeStream), _endOfData(false), _buffer(0),
+ _isCompressed(_shiftValue != 0xFF), _prevValue(0) {
+ // Setup our buffer for readBuffer
+ _buffer = new byte[kSampleBufferLength * (_isCompressed ? 1 : 2)];
+ assert(_buffer);
+}
+
+NeverhoodAudioStream::~NeverhoodAudioStream() {
+ delete[] _buffer;
+}
+
+int NeverhoodAudioStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samplesLeft = numSamples;
+
+ while (samplesLeft > 0 && !_endOfData) {
+
+ const int maxSamples = MIN<int>(kSampleBufferLength, samplesLeft);
+ const int bytesToRead = maxSamples * (_isCompressed ? 1 : 2);
+ int bytesRead = _stream->read(_buffer, bytesToRead);
+ int samplesRead = bytesRead / (_isCompressed ? 1 : 2);
+
+ samplesLeft -= samplesRead;
+
+ const byte *src = _buffer;
+ if (_isCompressed) {
+ while (samplesRead--) {
+ _prevValue += (int8)(*src++);
+ *buffer++ = _prevValue << _shiftValue;
+ }
+ } else {
+ memcpy(buffer, _buffer, bytesRead);
+ buffer += bytesRead;
+ }
+
+ if (bytesRead < bytesToRead || _stream->pos() >= _stream->size() || _stream->err() || _stream->eos()) {
+ if (_isLooping)
+ _stream->seek(0);
+ else
+ _endOfData = true;
+ }
+
+ }
+
+ return numSamples - samplesLeft;
+}
+
+AudioResourceMan::AudioResourceMan(NeverhoodEngine *vm)
+ : _vm(vm) {
+}
+
+AudioResourceMan::~AudioResourceMan() {
+}
+
+int16 AudioResourceMan::addSound(uint32 fileHash) {
+ AudioResourceManSoundItem *soundItem = new AudioResourceManSoundItem();
+ soundItem->_resourceHandle = _vm->_res->useResource(fileHash);
+ soundItem->_fileHash = fileHash;
+ soundItem->_data = NULL;
+ soundItem->_isLoaded = false;
+ soundItem->_isPlaying = false;
+ soundItem->_volume = 100;
+ soundItem->_panning = 50;
+
+ for (uint i = 0; i < _soundItems.size(); ++i)
+ if (!_soundItems[i]) {
+ _soundItems[i] = soundItem;
+ return i;
+ }
+
+ int16 soundIndex = (int16)_soundItems.size();
+ _soundItems.push_back(soundItem);
+ return soundIndex;
+}
+
+void AudioResourceMan::removeSound(int16 soundIndex) {
+ AudioResourceManSoundItem *soundItem = _soundItems[soundIndex];
+ if (soundItem->_data) {
+ _vm->_res->unloadResource(soundItem->_resourceHandle);
+ soundItem->_data = NULL;
+ }
+ if (soundItem->_resourceHandle != 1) {
+ _vm->_res->unuseResource(soundItem->_resourceHandle);
+ soundItem->_resourceHandle = -1;
+ }
+ if (_vm->_mixer->isSoundHandleActive(soundItem->_soundHandle))
+ _vm->_mixer->stopHandle(soundItem->_soundHandle);
+ delete soundItem;
+ _soundItems[soundIndex] = NULL;
+}
+
+void AudioResourceMan::loadSound(int16 soundIndex) {
+ AudioResourceManSoundItem *soundItem = _soundItems[soundIndex];
+ if (!soundItem->_data) {
+ // TODO Check if it's a sound resource
+ soundItem->_data = _vm->_res->loadResource(soundItem->_resourceHandle);
+ }
+}
+
+void AudioResourceMan::unloadSound(int16 soundIndex) {
+ AudioResourceManSoundItem *soundItem = _soundItems[soundIndex];
+ if (soundItem->_data) {
+ _vm->_res->unloadResource(soundItem->_resourceHandle);
+ soundItem->_data = NULL;
+ }
+}
+
+void AudioResourceMan::setSoundVolume(int16 soundIndex, int16 volume) {
+ AudioResourceManSoundItem *soundItem = _soundItems[soundIndex];
+ soundItem->_volume = MIN<int16>(volume, 100);
+ if (soundItem->_isPlaying && _vm->_mixer->isSoundHandleActive(soundItem->_soundHandle))
+ _vm->_mixer->setChannelVolume(soundItem->_soundHandle, VOLUME(soundItem->_volume));
+}
+
+void AudioResourceMan::setSoundPan(int16 soundIndex, int16 pan) {
+ AudioResourceManSoundItem *soundItem = _soundItems[soundIndex];
+ soundItem->_panning = MIN<int16>(pan, 100);
+ if (soundItem->_isPlaying && _vm->_mixer->isSoundHandleActive(soundItem->_soundHandle))
+ _vm->_mixer->setChannelVolume(soundItem->_soundHandle, PANNING(soundItem->_panning));
+}
+
+void AudioResourceMan::playSound(int16 soundIndex, bool looping) {
+ AudioResourceManSoundItem *soundItem = _soundItems[soundIndex];
+ if (!soundItem->_data)
+ loadSound(soundIndex);
+
+ uint32 soundSize = _vm->_res->getResourceSize(soundItem->_resourceHandle);
+ Common::MemoryReadStream *stream = new Common::MemoryReadStream(soundItem->_data, soundSize, DisposeAfterUse::NO);
+ byte *shiftValue = _vm->_res->getResourceExtData(soundItem->_resourceHandle);
+ NeverhoodAudioStream *audioStream = new NeverhoodAudioStream(22050, *shiftValue, false, DisposeAfterUse::YES, stream);
+
+ _vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &soundItem->_soundHandle,
+ audioStream, -1, VOLUME(soundItem->_volume), PANNING(soundItem->_panning));
+
+ debug("playing sound %08X", soundItem->_fileHash);
+
+ soundItem->_isPlaying = true;
+
+}
+
+void AudioResourceMan::stopSound(int16 soundIndex) {
+ AudioResourceManSoundItem *soundItem = _soundItems[soundIndex];
+ if (_vm->_mixer->isSoundHandleActive(soundItem->_soundHandle))
+ _vm->_mixer->stopHandle(soundItem->_soundHandle);
+ soundItem->_isPlaying = false;
+}
+
+bool AudioResourceMan::isSoundPlaying(int16 soundIndex) {
+ AudioResourceManSoundItem *soundItem = _soundItems[soundIndex];
+ return soundItem->_isPlaying;
+}
+
+int16 AudioResourceMan::loadMusic(uint32 fileHash) {
+
+ AudioResourceManMusicItem *musicItem;
+
+ for (uint i = 0; i < _musicItems.size(); ++i) {
+ musicItem = _musicItems[i];
+ if (musicItem && musicItem->_fileHash == fileHash && musicItem->_remove) {
+ musicItem->_remove = false;
+ musicItem->_isFadingOut = false;
+ musicItem->_isFadingIn = true;
+ return i;
+ }
+ }
+
+ musicItem = new AudioResourceManMusicItem();
+ musicItem->_fileHash = fileHash;
+ musicItem->_isPlaying = false;
+ musicItem->_remove = false;
+ musicItem->_volume = 100;
+ musicItem->_panning = 50;
+ musicItem->_start = false;
+ musicItem->_isFadingIn = false;
+ musicItem->_isFadingOut = false;
+
+ for (uint i = 0; i < _musicItems.size(); ++i) {
+ if (!_musicItems[i]) {
+ _musicItems[i] = musicItem;
+ return i;
+ }
+ }
+
+ int16 musicIndex = _musicItems.size();
+ _musicItems.push_back(musicItem);
+ return musicIndex;
+
+}
+
+void AudioResourceMan::unloadMusic(int16 musicIndex) {
+ AudioResourceManMusicItem *musicItem = _musicItems[musicIndex];
+ if (musicItem->_isFadingOut) {
+ musicItem->_remove = true;
+ } else {
+ if (_vm->_mixer->isSoundHandleActive(musicItem->_soundHandle))
+ _vm->_mixer->stopHandle(musicItem->_soundHandle);
+ musicItem->_isPlaying = false;
+ _musicItems[musicIndex] = NULL;
+ }
+}
+
+void AudioResourceMan::setMusicVolume(int16 musicIndex, int16 volume) {
+ AudioResourceManMusicItem *musicItem = _musicItems[musicIndex];
+ musicItem->_volume = MIN<int16>(volume, 100);
+ if (musicItem->_isPlaying && _vm->_mixer->isSoundHandleActive(musicItem->_soundHandle))
+ _vm->_mixer->setChannelVolume(musicItem->_soundHandle, VOLUME(musicItem->_volume));
+}
+
+void AudioResourceMan::playMusic(int16 musicIndex, int16 fadeVolumeStep) {
+ AudioResourceManMusicItem *musicItem = _musicItems[musicIndex];
+ if (!musicItem->_isPlaying) {
+ musicItem->_isFadingIn = false;
+ musicItem->_isFadingOut = false;
+ if (fadeVolumeStep != 0) {
+ musicItem->_isFadingIn = true;
+ musicItem->_fadeVolume = 0;
+ musicItem->_fadeVolumeStep = fadeVolumeStep;
+ }
+ musicItem->_start = true;
+ }
+}
+
+void AudioResourceMan::stopMusic(int16 musicIndex, int16 fadeVolumeStep) {
+ AudioResourceManMusicItem *musicItem = _musicItems[musicIndex];
+ if (_vm->_mixer->isSoundHandleActive(musicItem->_soundHandle)) {
+ if (fadeVolumeStep != 0) {
+ if (musicItem->_isFadingIn)
+ musicItem->_isFadingIn = false;
+ else
+ musicItem->_fadeVolume = musicItem->_volume;
+ musicItem->_isFadingOut = true;
+ musicItem->_fadeVolumeStep = fadeVolumeStep;
+ } else {
+ _vm->_mixer->stopHandle(musicItem->_soundHandle);
+ }
+ musicItem->_isPlaying = false;
+ }
+}
+
+bool AudioResourceMan::isMusicPlaying(int16 musicIndex) {
+ AudioResourceManMusicItem *musicItem = _musicItems[musicIndex];
+ return musicItem->_isPlaying;
+}
+
+void AudioResourceMan::updateMusicItem(int16 musicIndex) {
+ AudioResourceManMusicItem *musicItem = _musicItems[musicIndex];
+
+ if (musicItem->_start && !_vm->_mixer->isSoundHandleActive(musicItem->_soundHandle)) {
+ Common::SeekableReadStream *stream = _vm->_res->createStream(musicItem->_fileHash);
+ byte *shiftValue = _vm->_res->getResourceExtDataByHash(musicItem->_fileHash);
+ NeverhoodAudioStream *audioStream = new NeverhoodAudioStream(22050, *shiftValue, true, DisposeAfterUse::YES, stream);
+ _vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, &musicItem->_soundHandle,
+ audioStream, -1, VOLUME(musicItem->_isFadingIn ? musicItem->_fadeVolume : musicItem->_volume),
+ PANNING(musicItem->_panning));
+ musicItem->_start = false;
+ musicItem->_isPlaying = true;
+ }
+
+ if (_vm->_mixer->isSoundHandleActive(musicItem->_soundHandle)) {
+ if (musicItem->_isFadingIn) {
+ musicItem->_fadeVolume += musicItem->_fadeVolumeStep;
+ if (musicItem->_fadeVolume >= musicItem->_volume) {
+ musicItem->_fadeVolume = musicItem->_volume;
+ musicItem->_isFadingIn = false;
+ }
+ _vm->_mixer->setChannelVolume(musicItem->_soundHandle, VOLUME(musicItem->_fadeVolume));
+ }
+ if (musicItem->_isFadingOut) {
+ musicItem->_fadeVolume -= musicItem->_fadeVolumeStep;
+ if (musicItem->_fadeVolume < 0)
+ musicItem->_fadeVolume = 0;
+ _vm->_mixer->setChannelVolume(musicItem->_soundHandle, VOLUME(musicItem->_fadeVolume));
+ if (musicItem->_fadeVolume == 0) {
+ musicItem->_isFadingOut = false;
+ stopMusic(musicIndex, 0);
+ if (musicItem->_remove)
+ unloadMusic(musicIndex);
+ }
+ }
+ }
+
+}
+
+void AudioResourceMan::update() {
+ for (uint i = 0; i < _musicItems.size(); ++i)
+ if (_musicItems[i])
+ updateMusicItem(i);
}
} // End of namespace Neverhood