scummvm/engines/kyra/sound.cpp
Johannes Schickel 0bea9cf47b Made the kyra debug extensions more generic, i.e. scumm engine could replace
their debugC calls now with the new introduced debugC calls.
(A mail how to use it will follow shortly on -devel)
Also now these special engine debug flags can be specified from the commandline.
Also made the -c & --config parameter check more secure.

svn-id: r20695
2006-02-14 23:31:25 +00:00

478 lines
12 KiB
C++

/* 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) {
debugC(9, kDebugLevelMain, "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) {
debugC(9, kDebugLevelMain, "KyraEngine::snd_setSoundEffectFile(%d)", file);
assert(file < _xmidiFilesCount);
_sound->loadSoundEffectFile(_xmidiFiles[file]);
}
void KyraEngine::snd_playSoundEffect(int track) {
debugC(9, kDebugLevelMain, "KyraEngine::snd_playSoundEffect(%d)", track);
if (track == 49) {
snd_playWanderScoreViaMap(56, 1);
} else {
_sound->playSoundEffect(track);
}
}
void KyraEngine::snd_playWanderScoreViaMap(int command, int restart) {
debugC(9, kDebugLevelMain, "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) {
debugC(9, kDebugLevelMain, "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) {
debugC(9, kDebugLevelMain, "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