diff options
Diffstat (limited to 'engines/kyra/sound.cpp')
-rw-r--r-- | engines/kyra/sound.cpp | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp new file mode 100644 index 0000000000..48e59a5d15 --- /dev/null +++ b/engines/kyra/sound.cpp @@ -0,0 +1,477 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/stdafx.h" +#include "common/system.h" +#include "kyra/resource.h" +#include "kyra/sound.h" + +#include "sound/mixer.h" +#include "sound/voc.h" +#include "sound/audiostream.h" + +#include "sound/mp3.h" +#include "sound/vorbis.h" +#include "sound/flac.h" + +namespace Kyra { + +SoundPC::SoundPC(MidiDriver *driver, Audio::Mixer *mixer, KyraEngine *engine) : Sound() { + _engine = engine; + _driver = driver; + _passThrough = false; + _eventFromMusic = false; + _fadeMusicOut = _sfxIsPlaying = false; + _fadeStartTime = 0; + _isPlaying = _isLooping = _nativeMT32 = false; + _soundEffect = _parser = 0; + _soundEffectSource = _parserSource = 0; + + memset(_channel, 0, sizeof(MidiChannel*) * 32); + memset(_channelVolume, 50, sizeof(uint8) * 16); + _channelVolume[10] = 100; + for (int i = 0; i < 16; ++i) { + _virChannel[i] = i; + } + _volume = 0; + + int ret = open(); + if (ret != MERR_ALREADY_OPEN && ret != 0) { + error("couldn't open midi driver"); + } + + _currentVocFile = 0; + _mixer = mixer; +} + +SoundPC::~SoundPC() { + _driver->setTimerCallback(NULL, NULL); + close(); +} + +void SoundPC::setVolume(int volume) { + if (volume < 0) + volume = 0; + else if (volume > 255) + volume = 255; + + if (_volume == volume) + return; + + _volume = volume; + for (int i = 0; i < 32; ++i) { + if (_channel[i]) { + if (i >= 16) { + _channel[i]->volume(_channelVolume[i - 16] * _volume / 255); + } else { + _channel[i]->volume(_channelVolume[i] * _volume / 255); + } + } + } +} + +int SoundPC::open() { + // Don't ever call open without first setting the output driver! + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + _driver->setTimerCallback(this, &onTimer); + return 0; +} + +void SoundPC::close() { + if (_driver) + _driver->close(); + _driver = 0; +} + +void SoundPC::send(uint32 b) { + if (_passThrough) { + if ((b & 0xFFF0) == 0x007BB0) + return; + _driver->send(b); + return; + } + + uint8 channel = (byte)(b & 0x0F); + if (((b & 0xFFF0) == 0x6FB0 || (b & 0xFFF0) == 0x6EB0) && channel != 9) { + if (_virChannel[channel] == channel) { + _virChannel[channel] = channel + 16; + if (!_channel[_virChannel[channel]]) + _channel[_virChannel[channel]] = _driver->allocateChannel(); + if (_channel[_virChannel[channel]]) + _channel[_virChannel[channel]]->volume(_channelVolume[channel] * _volume / 255); + } + return; + } + + if ((b & 0xFFF0) == 0x07B0) { + // Adjust volume changes by master volume + uint8 volume = (uint8)((b >> 16) & 0x7F); + _channelVolume[channel] = volume; + volume = volume * _volume / 255; + b = (b & 0xFF00FFFF) | (volume << 16); + } else if ((b & 0xF0) == 0xC0 && !_nativeMT32) { + b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; + } else if ((b & 0xFFF0) == 0x007BB0) { + //Only respond to All Notes Off if this channel + //has currently been allocated + if (!_channel[channel]) + return; + } + + if (!_channel[_virChannel[channel]]) { + _channel[_virChannel[channel]] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + if (_channel[_virChannel[channel]]) + _channel[_virChannel[channel]]->volume(_channelVolume[channel] * _volume / 255); + } + if (_channel[_virChannel[channel]]) + _channel[_virChannel[channel]]->send(b); +} + +void SoundPC::metaEvent(byte type, byte *data, uint16 length) { + switch (type) { + case 0x2F: // End of Track + if (_eventFromMusic) { + if (!_isLooping) { + _isPlaying = false; + } + // remap all channels + for (int i = 0; i < 16; ++i) { + _virChannel[i] = i; + } + } else { + _sfxIsPlaying = false; + } + break; + default: + _driver->metaEvent(type, data, length); + break; + } +} + +void SoundPC::playMusic(const char *file) { + uint32 size; + uint8 *data = (_engine->resource())->fileData(file, &size); + + if (!data) { + warning("couldn't load '%s'", file); + return; + } + + playMusic(data, size); +} + +void SoundPC::playMusic(uint8 *data, uint32 size) { + stopMusic(); + + _parserSource = data; + _parser = MidiParser::createParser_XMIDI(); + assert(_parser); + + if (!_parser->loadMusic(data, size)) { + warning("Error reading track!"); + delete _parser; + _parser = 0; + return; + } + + _parser->setTrack(0); + _parser->setMidiDriver(this); + _parser->setTimerRate(getBaseTempo()); + _parser->property(MidiParser::mpAutoLoop, false); +} + +void SoundPC::loadSoundEffectFile(const char *file) { + uint32 size; + uint8 *data = (_engine->resource())->fileData(file, &size); + + if (!data) { + warning("couldn't load '%s'", file); + return; + } + + loadSoundEffectFile(data, size); +} + +void SoundPC::loadSoundEffectFile(uint8 *data, uint32 size) { + stopSoundEffect(); + + _soundEffectSource = data; + _soundEffect = MidiParser::createParser_XMIDI(); + assert(_soundEffect); + + if (!_soundEffect->loadMusic(data, size)) { + warning("Error reading track!"); + delete _parser; + _parser = 0; + return; + } + + _soundEffect->setTrack(0); + _soundEffect->setMidiDriver(this); + _soundEffect->setTimerRate(getBaseTempo()); + _soundEffect->property(MidiParser::mpAutoLoop, false); +} + +void SoundPC::stopMusic() { + _isLooping = false; + _isPlaying = false; + if (_parser) { + _parser->unloadMusic(); + delete _parser; + _parser = 0; + delete [] _parserSource; + _parserSource = 0; + + _fadeStartTime = 0; + _fadeMusicOut = false; + setVolume(255); + } +} + +void SoundPC::stopSoundEffect() { + _sfxIsPlaying = false; + if (_soundEffect) { + _soundEffect->unloadMusic(); + delete _soundEffect; + _soundEffect = 0; + delete [] _soundEffectSource; + _soundEffectSource = 0; + } +} + +void SoundPC::onTimer(void *refCon) { + SoundPC *music = (SoundPC *)refCon; + + // this should be set to the fadeToBlack value + static const uint32 musicFadeTime = 2 * 1000; + + if (music->_fadeMusicOut && music->_fadeStartTime + musicFadeTime > music->_engine->_system->getMillis()) { + byte volume = (byte)((musicFadeTime - (music->_engine->_system->getMillis() - music->_fadeStartTime)) * 255 / musicFadeTime); + music->setVolume(volume); + } else if(music->_fadeStartTime) { + music->setVolume(255); + music->_fadeStartTime = 0; + music->_fadeMusicOut = false; + music->_isLooping = false; + music->_isPlaying = false; + + music->_eventFromMusic = true; + // from sound/midiparser.cpp + for (int i = 0; i < 128; ++i) { + for (int j = 0; j < 16; ++j) { + music->send(0x80 | j | i << 8); + } + } + for (int i = 0; i < 16; ++i) { + music->send(0x007BB0 | i); + } + } + + if (music->_isPlaying) { + if (music->_parser) { + music->_eventFromMusic = true; + music->_parser->onTimer(); + } + } + + if (music->_sfxIsPlaying) { + if (music->_soundEffect) { + music->_eventFromMusic = false; + music->_soundEffect->onTimer(); + } + } +} + +void SoundPC::playTrack(uint8 track, bool loop) { + if (_parser) { + _isPlaying = true; + _isLooping = loop; + _parser->setTrack(track); + _parser->jumpToTick(0); + _parser->setTempo(1); + _parser->property(MidiParser::mpAutoLoop, loop); + } +} + +void SoundPC::playSoundEffect(uint8 track) { + if (_soundEffect) { + _sfxIsPlaying = true; + _soundEffect->setTrack(track); + _soundEffect->jumpToTick(0); + _soundEffect->property(MidiParser::mpAutoLoop, false); + } +} + +void SoundPC::beginFadeOut() { + // this should be something like fade out... + _fadeMusicOut = true; + _fadeStartTime = _engine->_system->getMillis(); +} + +void SoundPC::voicePlay(const char *file) { + uint32 fileSize = 0; + byte *fileData = 0; + bool found = false; + char filenamebuffer[25]; + + for (int i = 0; _supportedCodes[i].fileext; ++i) { + strcpy(filenamebuffer, file); + strcat(filenamebuffer, _supportedCodes[i].fileext); + + _engine->resource()->fileHandle(filenamebuffer, &fileSize, _compressHandle); + if (!_compressHandle.isOpen()) + continue; + + _currentVocFile = _supportedCodes[i].streamFunc(&_compressHandle, fileSize); + found = true; + break; + } + + if (!found) { + strcpy(filenamebuffer, file); + strcat(filenamebuffer, ".VOC"); + + fileData = _engine->resource()->fileData(filenamebuffer, &fileSize); + if (!fileData) + return; + + Common::MemoryReadStream vocStream(fileData, fileSize); + _mixer->stopHandle(_vocHandle); + _currentVocFile = makeVOCStream(vocStream); + } + + if (_currentVocFile) + _mixer->playInputStream(Audio::Mixer::kSpeechSoundType, &_vocHandle, _currentVocFile); + delete [] fileData; + fileSize = 0; +} + +bool SoundPC::voiceIsPlaying() { + return _mixer->isSoundHandleActive(_vocHandle); +} + +void KyraEngine::snd_playTheme(int file, int track) { + debug(9, "KyraEngine::snd_playTheme(%d)", file); + assert(file < _xmidiFilesCount); + _curMusicTheme = _newMusicTheme = file; + _sound->playMusic(_xmidiFiles[file]); + _sound->playTrack(track, false); +} + +void KyraEngine::snd_setSoundEffectFile(int file) { + debug(9, "KyraEngine::snd_setSoundEffectFile(%d)", file); + assert(file < _xmidiFilesCount); + _sound->loadSoundEffectFile(_xmidiFiles[file]); +} + +void KyraEngine::snd_playSoundEffect(int track) { + debug(9, "KyraEngine::snd_playSoundEffect(%d)", track); + if (track == 49) { + snd_playWanderScoreViaMap(56, 1); + } else { + _sound->playSoundEffect(track); + } +} + +void KyraEngine::snd_playWanderScoreViaMap(int command, int restart) { + debug(9, "KyraEngine::snd_playWanderScoreViaMap(%d, %d)", command, restart); + static const int8 soundTable[] = { + -1, 0, -1, 1, 0, 3, 0, 2, + 0, 4, 1, 2, 1, 3, 1, 4, + 1, 0x5C, 1, 6, 1, 7, 2, 2, + 2, 3, 2, 4, 2, 5, 2, 6, + 2, 7, 3, 3, 3, 4, 1, 8, + 1, 9, 4, 2, 4, 3, 4, 4, + 4, 5, 4, 6, 4, 7, 4, 8, + 1, 0x0B, 1, 0x0C, 1, 0x0E, 1, 0x0D, + 4, 9, 5, 0x0C, 6, 2, 6, 6, + 6, 7, 6, 8, 6, 9, 6, 3, + 6, 4, 6, 5, 7, 2, 7, 3, + 7, 4, 7, 5, 7, 6, 7, 7, + 7, 8, 7, 9, 8, 2, 8, 3, + 8, 4, 8, 5, 6, 0x0B, 5, 0x0B + }; + //if (!_disableSound) { + // XXX + //} + assert(command*2+1 < ARRAYSIZE(soundTable)); + if (_curMusicTheme != soundTable[command*2]+1) { + if (soundTable[command*2] != -1) { + snd_playTheme(soundTable[command*2]+1); + } + } + + if (restart) + _lastMusicCommand = -1; + + if (command != 1) { + if (_lastMusicCommand != command) { + _lastMusicCommand = command; + _sound->playTrack(soundTable[command*2+1], true); + } + } else { + _lastMusicCommand = 1; + _sound->beginFadeOut(); + } +} + +void KyraEngine::snd_playVoiceFile(int id) { + debug(9, "KyraEngine::snd_playVoiceFile(%d)", id); + char vocFile[9]; + assert(id >= 0 && id < 9999); + sprintf(vocFile, "%03d", id); + _sound->voicePlay(vocFile); +} + +void KyraEngine::snd_voiceWaitForFinish(bool ingame) { + debug(9, "KyraEngine::snd_voiceWaitForFinish(%d)", ingame); + while (_sound->voiceIsPlaying() && !_skipFlag) { + if (ingame) { + delay(10, true); + } else { + _system->delayMillis(10); + } + } +} + +// static res + +const SoundPC::SpeechCodecs SoundPC::_supportedCodes[] = { +#ifdef USE_MAD + { ".VO3", makeMP3Stream }, +#endif // USE_MAD +#ifdef USE_VORBIS + { ".VOG", makeVorbisStream }, +#endif // USE_VORBIS +#ifdef USE_FLAC + { ".VOF", makeFlacStream }, +#endif // USE_FLAC + { 0, 0 } +}; + +} // end of namespace Kyra |