mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-22 09:49:11 +00:00
a2367d26f9
svn-id: r54392
589 lines
16 KiB
C++
589 lines
16 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 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$
|
|
*
|
|
* sound functionality
|
|
*/
|
|
|
|
#include "tinsel/sound.h"
|
|
|
|
#include "tinsel/dw.h"
|
|
#include "tinsel/config.h"
|
|
#include "tinsel/music.h"
|
|
#include "tinsel/strres.h"
|
|
#include "tinsel/tinsel.h"
|
|
#include "tinsel/sysvar.h"
|
|
#include "tinsel/background.h"
|
|
|
|
#include "common/config-manager.h"
|
|
#include "common/endian.h"
|
|
#include "common/file.h"
|
|
#include "common/memstream.h"
|
|
#include "common/system.h"
|
|
|
|
#include "sound/mixer.h"
|
|
#include "sound/decoders/adpcm.h"
|
|
#include "sound/decoders/flac.h"
|
|
#include "sound/decoders/mp3.h"
|
|
#include "sound/decoders/raw.h"
|
|
#include "sound/decoders/vag.h"
|
|
#include "sound/decoders/vorbis.h"
|
|
|
|
#include "gui/message.h"
|
|
|
|
namespace Tinsel {
|
|
|
|
extern LANGUAGE sampleLanguage;
|
|
|
|
//--------------------------- General data ----------------------------------
|
|
|
|
SoundManager::SoundManager(TinselEngine *vm) :
|
|
//_vm(vm), // TODO: Enable this once global _vm var is gone
|
|
_sampleIndex(0), _sampleIndexLen(0),
|
|
_soundMode(kVOCMode) {
|
|
|
|
for (int i = 0; i < kNumChannels; i++)
|
|
_channels[i].sampleNum = _channels[i].subSample = -1;
|
|
}
|
|
|
|
SoundManager::~SoundManager() {
|
|
free(_sampleIndex);
|
|
}
|
|
|
|
/**
|
|
* Plays the specified sample through the sound driver.
|
|
* @param id Identifier of sample to be played
|
|
* @param type type of sound (voice or sfx)
|
|
* @param handle sound handle
|
|
*/
|
|
// playSample for DiscWorld 1
|
|
bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle) {
|
|
// Floppy version has no sample file
|
|
if (_vm->getFeatures() & GF_FLOPPY)
|
|
return false;
|
|
|
|
// no sample driver?
|
|
if (!_vm->_mixer->isReady())
|
|
return false;
|
|
|
|
Channel &curChan = _channels[kChannelTinsel1];
|
|
|
|
// stop any currently playing sample
|
|
_vm->_mixer->stopHandle(curChan.handle);
|
|
|
|
// make sure id is in range
|
|
assert(id > 0 && id < _sampleIndexLen);
|
|
|
|
curChan.sampleNum = id;
|
|
curChan.subSample = 0;
|
|
|
|
// get file offset for this sample
|
|
uint32 dwSampleIndex = _sampleIndex[id];
|
|
|
|
// move to correct position in the sample file
|
|
_sampleStream.seek(dwSampleIndex);
|
|
if (_sampleStream.eos() || _sampleStream.err() || (uint32)_sampleStream.pos() != dwSampleIndex)
|
|
error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage));
|
|
|
|
// read the length of the sample
|
|
uint32 sampleLen = _sampleStream.readUint32();
|
|
if (_sampleStream.eos() || _sampleStream.err())
|
|
error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage));
|
|
|
|
if (TinselV1PSX) {
|
|
// Read the stream and create a VAG Audio stream
|
|
Audio::AudioStream *vagStream = Audio::makeVagStream(_sampleStream.readStream(sampleLen), 44100);
|
|
|
|
// FIXME: Should set this in a different place ;)
|
|
_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, _vm->_config->_soundVolume);
|
|
//_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic);
|
|
_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, _vm->_config->_voiceVolume);
|
|
|
|
// Play the audio stream
|
|
_vm->_mixer->playStream(type, &curChan.handle, vagStream);
|
|
} else {
|
|
// allocate a buffer
|
|
byte *sampleBuf = (byte *)malloc(sampleLen);
|
|
assert(sampleBuf);
|
|
|
|
// read all of the sample
|
|
if (_sampleStream.read(sampleBuf, sampleLen) != sampleLen)
|
|
error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage));
|
|
|
|
// FIXME: Should set this in a different place ;)
|
|
bool mute = false;
|
|
if (ConfMan.hasKey("mute"))
|
|
mute = ConfMan.getBool("mute");
|
|
|
|
_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, mute ? 0 : _vm->_config->_soundVolume);
|
|
//_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic);
|
|
_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, mute ? 0 : _vm->_config->_voiceVolume);
|
|
|
|
Audio::AudioStream *sampleStream = 0;
|
|
|
|
// play it
|
|
switch (_soundMode) {
|
|
case kMP3Mode:
|
|
#ifdef USE_MAD
|
|
{
|
|
Common::MemoryReadStream *compressedStream =
|
|
new Common::MemoryReadStream(sampleBuf, sampleLen, DisposeAfterUse::YES);
|
|
sampleStream = Audio::makeMP3Stream(compressedStream, DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
break;
|
|
case kVorbisMode:
|
|
#ifdef USE_VORBIS
|
|
{
|
|
Common::MemoryReadStream *compressedStream =
|
|
new Common::MemoryReadStream(sampleBuf, sampleLen, DisposeAfterUse::YES);
|
|
sampleStream = Audio::makeVorbisStream(compressedStream, DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
break;
|
|
case kFLACMode:
|
|
#ifdef USE_FLAC
|
|
{
|
|
Common::MemoryReadStream *compressedStream =
|
|
new Common::MemoryReadStream(sampleBuf, sampleLen, DisposeAfterUse::YES);
|
|
sampleStream = Audio::makeFLACStream(compressedStream, DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
break;
|
|
default:
|
|
sampleStream = Audio::makeRawStream(sampleBuf, sampleLen, 22050, Audio::FLAG_UNSIGNED);
|
|
break;
|
|
}
|
|
if (sampleStream) {
|
|
_vm->_mixer->playStream(type, &curChan.handle, sampleStream);
|
|
}
|
|
}
|
|
|
|
if (handle)
|
|
*handle = curChan.handle;
|
|
|
|
return true;
|
|
}
|
|
|
|
// playSample for DiscWorld 2
|
|
bool SoundManager::playSample(int id, int sub, bool bLooped, int x, int y, int priority,
|
|
Audio::Mixer::SoundType type, Audio::SoundHandle *handle) {
|
|
|
|
// Floppy version has no sample file
|
|
if (_vm->getFeatures() & GF_FLOPPY)
|
|
return false;
|
|
|
|
// no sample driver?
|
|
if (!_vm->_mixer->isReady())
|
|
return false;
|
|
|
|
Channel *curChan;
|
|
|
|
uint8 sndVol = 255;
|
|
|
|
// Sample on screen?
|
|
if (!offscreenChecks(x, y))
|
|
return false;
|
|
|
|
// If that sample is already playing, stop it
|
|
stopSpecSample(id, sub);
|
|
|
|
if (type == Audio::Mixer::kSpeechSoundType) {
|
|
curChan = &_channels[kChannelTalk];
|
|
} else if (type == Audio::Mixer::kSFXSoundType) {
|
|
uint32 oldestTime = g_system->getMillis();
|
|
int oldestChan = kChannelSFX;
|
|
|
|
int chan;
|
|
for (chan = kChannelSFX; chan < kNumChannels; chan++) {
|
|
if (!_vm->_mixer->isSoundHandleActive(_channels[chan].handle))
|
|
break;
|
|
|
|
if ((_channels[chan].lastStart < oldestTime) &&
|
|
(_channels[chan].priority <= priority)) {
|
|
|
|
oldestTime = _channels[chan].lastStart;
|
|
oldestChan = chan;
|
|
}
|
|
}
|
|
|
|
if (chan == kNumChannels) {
|
|
if (_channels[oldestChan].priority > priority) {
|
|
warning("playSample: No free channel");
|
|
return false;
|
|
}
|
|
|
|
chan = oldestChan;
|
|
}
|
|
|
|
if (_vm->_pcmMusic->isDimmed() && SysVar(SYS_SceneFxDimFactor))
|
|
sndVol = 255 - 255/SysVar(SYS_SceneFxDimFactor);
|
|
|
|
curChan = &_channels[chan];
|
|
} else {
|
|
warning("playSample: Unknown SoundType");
|
|
return false;
|
|
}
|
|
|
|
// stop any currently playing sample
|
|
_vm->_mixer->stopHandle(curChan->handle);
|
|
|
|
// make sure id is in range
|
|
assert(id > 0 && id < _sampleIndexLen);
|
|
|
|
// get file offset for this sample
|
|
uint32 dwSampleIndex = _sampleIndex[id];
|
|
|
|
if (dwSampleIndex == 0) {
|
|
warning("Tinsel2 playSample, non-existant sample %d", id);
|
|
return false;
|
|
}
|
|
|
|
// move to correct position in the sample file
|
|
_sampleStream.seek(dwSampleIndex);
|
|
if (_sampleStream.eos() || _sampleStream.err() || (uint32)_sampleStream.pos() != dwSampleIndex)
|
|
error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage));
|
|
|
|
// read the length of the sample
|
|
uint32 sampleLen = _sampleStream.readUint32();
|
|
if (_sampleStream.eos() || _sampleStream.err())
|
|
error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage));
|
|
|
|
if (sampleLen & 0x80000000) {
|
|
// Has sub samples
|
|
|
|
int32 numSubs = sampleLen & ~0x80000000;
|
|
|
|
assert(sub >= 0 && sub < numSubs);
|
|
|
|
// Skipping
|
|
for (int32 i = 0; i < sub; i++) {
|
|
sampleLen = _sampleStream.readUint32();
|
|
_sampleStream.skip(sampleLen);
|
|
if (_sampleStream.eos() || _sampleStream.err())
|
|
error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage));
|
|
}
|
|
sampleLen = _sampleStream.readUint32();
|
|
if (_sampleStream.eos() || _sampleStream.err())
|
|
error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage));
|
|
}
|
|
|
|
debugC(DEBUG_DETAILED, kTinselDebugSound, "Playing sound %d.%d, %d bytes at %d (pan %d)", id, sub, sampleLen,
|
|
_sampleStream.pos(), getPan(x));
|
|
|
|
// allocate a buffer
|
|
byte *sampleBuf = (byte *) malloc(sampleLen);
|
|
assert(sampleBuf);
|
|
|
|
// read all of the sample
|
|
if (_sampleStream.read(sampleBuf, sampleLen) != sampleLen)
|
|
error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage));
|
|
|
|
Common::MemoryReadStream *compressedStream =
|
|
new Common::MemoryReadStream(sampleBuf, sampleLen, DisposeAfterUse::YES);
|
|
Audio::AudioStream *sampleStream = 0;
|
|
|
|
switch (_soundMode) {
|
|
case kMP3Mode:
|
|
#ifdef USE_MAD
|
|
sampleStream = Audio::makeMP3Stream(compressedStream, DisposeAfterUse::YES);
|
|
#endif
|
|
break;
|
|
case kVorbisMode:
|
|
#ifdef USE_VORBIS
|
|
sampleStream = Audio::makeVorbisStream(compressedStream, DisposeAfterUse::YES);
|
|
#endif
|
|
break;
|
|
case kFLACMode:
|
|
#ifdef USE_FLAC
|
|
sampleStream = Audio::makeFLACStream(compressedStream, DisposeAfterUse::YES);
|
|
#endif
|
|
break;
|
|
default:
|
|
sampleStream = Audio::makeADPCMStream(compressedStream, DisposeAfterUse::YES, sampleLen, Audio::kADPCMTinsel6, 22050, 1, 24);
|
|
break;
|
|
}
|
|
|
|
// FIXME: Should set this in a different place ;)
|
|
bool mute = false;
|
|
if (ConfMan.hasKey("mute"))
|
|
mute = ConfMan.getBool("mute");
|
|
|
|
_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, mute ? 0 : _vm->_config->_soundVolume);
|
|
//_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic);
|
|
_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, mute ? 0 : _vm->_config->_voiceVolume);
|
|
|
|
curChan->sampleNum = id;
|
|
curChan->subSample = sub;
|
|
curChan->looped = bLooped;
|
|
curChan->x = x;
|
|
curChan->y = y;
|
|
curChan->priority = priority;
|
|
curChan->lastStart = g_system->getMillis();
|
|
// /---Compression----\ Milis BytesPerSecond
|
|
// not needed and won't work when using MP3/OGG/FLAC anyway
|
|
//curChan->timeDuration = (((sampleLen * 64) / 25) * 1000) / (22050 * 2);
|
|
|
|
// Play it
|
|
_vm->_mixer->playStream(type, &curChan->handle, sampleStream);
|
|
|
|
_vm->_mixer->setChannelVolume(curChan->handle, sndVol);
|
|
_vm->_mixer->setChannelBalance(curChan->handle, getPan(x));
|
|
|
|
if (handle)
|
|
*handle = curChan->handle;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns FALSE if sample doesn't need playing
|
|
*/
|
|
bool SoundManager::offscreenChecks(int x, int &y) {
|
|
// No action if no x specification
|
|
if (x == -1)
|
|
return true;
|
|
|
|
// convert x to offset from screen centre
|
|
x -= PlayfieldGetCentreX(FIELD_WORLD);
|
|
|
|
if (x < -SCREEN_WIDTH || x > SCREEN_WIDTH) {
|
|
// A long way offscreen, ignore it
|
|
return false;
|
|
} else if (x < -SCREEN_WIDTH/2 || x > SCREEN_WIDTH/2) {
|
|
// Off-screen, attennuate it
|
|
|
|
y = (y > 0) ? (y / 2) : 50;
|
|
|
|
return true;
|
|
} else
|
|
return true;
|
|
}
|
|
|
|
int8 SoundManager::getPan(int x) {
|
|
|
|
if (x == -1)
|
|
return 0;
|
|
|
|
x -= PlayfieldGetCentreX(FIELD_WORLD);
|
|
|
|
if (x == 0)
|
|
return 0;
|
|
|
|
if (x < 0) {
|
|
if (x < (-SCREEN_WIDTH / 2))
|
|
return -127;
|
|
|
|
x = (-x * 127) / (SCREEN_WIDTH / 2);
|
|
|
|
return 0 - x;
|
|
}
|
|
|
|
if (x > (SCREEN_WIDTH / 2))
|
|
return 127;
|
|
|
|
x = (x * 127) / (SCREEN_WIDTH / 2);
|
|
|
|
return x;
|
|
}
|
|
|
|
/**
|
|
* Returns TRUE if there is a sample for the specified sample identifier.
|
|
* @param id Identifier of sample to be checked
|
|
*/
|
|
bool SoundManager::sampleExists(int id) {
|
|
if (_vm->_mixer->isReady()) {
|
|
// make sure id is in range
|
|
if (id > 0 && id < _sampleIndexLen) {
|
|
// check for a sample index
|
|
if (_sampleIndex[id])
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// no sample driver or no sample
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns true if a sample is currently playing.
|
|
*/
|
|
bool SoundManager::sampleIsPlaying(int id) {
|
|
if (!TinselV2)
|
|
return _vm->_mixer->isSoundHandleActive(_channels[kChannelTinsel1].handle);
|
|
|
|
for (int i = 0; i < kNumChannels; i++)
|
|
if (_channels[i].sampleNum == id)
|
|
if (_vm->_mixer->isSoundHandleActive(_channels[i].handle))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Stops any currently playing sample.
|
|
*/
|
|
void SoundManager::stopAllSamples() {
|
|
// stop currently playing sample
|
|
|
|
if (!TinselV2) {
|
|
_vm->_mixer->stopHandle(_channels[kChannelTinsel1].handle);
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < kNumChannels; i++)
|
|
_vm->_mixer->stopHandle(_channels[i].handle);
|
|
}
|
|
|
|
void SoundManager::stopSpecSample(int id, int sub) {
|
|
debugC(DEBUG_DETAILED, kTinselDebugSound, "stopSpecSample(%d, %d)", id, sub);
|
|
|
|
if (!TinselV2) {
|
|
if (_channels[kChannelTinsel1].sampleNum == id)
|
|
_vm->_mixer->stopHandle(_channels[kChannelTinsel1].handle);
|
|
return;
|
|
}
|
|
|
|
for (int i = kChannelTalk; i < kNumChannels; i++) {
|
|
if ((_channels[i].sampleNum == id) && (_channels[i].subSample == sub))
|
|
_vm->_mixer->stopHandle(_channels[i].handle);
|
|
}
|
|
}
|
|
|
|
void SoundManager::setSFXVolumes(uint8 volume) {
|
|
if (!TinselV2)
|
|
return;
|
|
|
|
for (int i = kChannelSFX; i < kNumChannels; i++)
|
|
_vm->_mixer->setChannelVolume(_channels[i].handle, volume);
|
|
}
|
|
|
|
/**
|
|
* Opens and inits all sound sample files.
|
|
*/
|
|
void SoundManager::openSampleFiles() {
|
|
// Floppy and demo versions have no sample files, except for the Discworld 2 demo
|
|
if (_vm->getFeatures() & GF_FLOPPY || (IsDemo && !TinselV2))
|
|
return;
|
|
|
|
TinselFile f;
|
|
|
|
if (_sampleIndex)
|
|
// already allocated
|
|
return;
|
|
|
|
// open sample index file in binary mode
|
|
if (f.open(_vm->getSampleIndex(sampleLanguage))) {
|
|
// get length of index file
|
|
f.seek(0, SEEK_END); // move to end of file
|
|
_sampleIndexLen = f.pos(); // get file pointer
|
|
f.seek(0, SEEK_SET); // back to beginning
|
|
|
|
if (_sampleIndex == NULL) {
|
|
// allocate a buffer for the indices
|
|
_sampleIndex = (uint32 *)malloc(_sampleIndexLen);
|
|
|
|
// make sure memory allocated
|
|
if (_sampleIndex == NULL) {
|
|
// disable samples if cannot alloc buffer for indices
|
|
// TODO: Disabled sound if we can't load the sample index?
|
|
return;
|
|
}
|
|
}
|
|
|
|
// load data
|
|
if (f.read(_sampleIndex, _sampleIndexLen) != (uint32)_sampleIndexLen)
|
|
// file must be corrupt if we get to here
|
|
error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage));
|
|
|
|
// close the file
|
|
f.close();
|
|
|
|
// convert file size to size in DWORDs
|
|
_sampleIndexLen /= sizeof(uint32);
|
|
|
|
#ifdef SCUMM_BIG_ENDIAN
|
|
// Convert all ids from LE to native format
|
|
for (int i = 0; i < _sampleIndexLen; ++i) {
|
|
_sampleIndex[i] = SWAP_BYTES_32(_sampleIndex[i]);
|
|
}
|
|
#endif
|
|
|
|
// Detect format of soundfile by looking at 1st sample-index
|
|
switch (TO_BE_32(_sampleIndex[0])) {
|
|
case MKID_BE('MP3 '):
|
|
debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected MP3 sound-data");
|
|
_soundMode = kMP3Mode;
|
|
break;
|
|
|
|
case MKID_BE('OGG '):
|
|
debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected OGG sound-data");
|
|
_soundMode = kVorbisMode;
|
|
break;
|
|
|
|
case MKID_BE('FLAC'):
|
|
debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected FLAC sound-data");
|
|
_soundMode = kFLACMode;
|
|
break;
|
|
|
|
default:
|
|
debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected original sound-data");
|
|
break;
|
|
}
|
|
// Normally the 1st sample-index points to nothing at all
|
|
_sampleIndex[0] = 0;
|
|
} else {
|
|
char buf[50];
|
|
sprintf(buf, CANNOT_FIND_FILE, _vm->getSampleIndex(sampleLanguage));
|
|
GUI::MessageDialog dialog(buf, "OK");
|
|
dialog.runModal();
|
|
|
|
error(CANNOT_FIND_FILE, _vm->getSampleIndex(sampleLanguage));
|
|
}
|
|
|
|
// open sample file in binary mode
|
|
if (!_sampleStream.open(_vm->getSampleFile(sampleLanguage))) {
|
|
char buf[50];
|
|
sprintf(buf, CANNOT_FIND_FILE, _vm->getSampleFile(sampleLanguage));
|
|
GUI::MessageDialog dialog(buf, "OK");
|
|
dialog.runModal();
|
|
|
|
error(CANNOT_FIND_FILE, _vm->getSampleFile(sampleLanguage));
|
|
}
|
|
|
|
/*
|
|
// gen length of the largest sample
|
|
sampleBuffer.size = _sampleStream.readUint32LE();
|
|
if (_sampleStream.eos() || _sampleStream.err())
|
|
error(FILE_IS_CORRUPT, _vm->getSampleFile(sampleLanguage));
|
|
*/
|
|
}
|
|
|
|
void SoundManager::closeSampleStream() {
|
|
_sampleStream.close();
|
|
free(_sampleIndex);
|
|
_sampleIndex = 0;
|
|
_sampleIndexLen = 0;
|
|
}
|
|
|
|
} // End of namespace Tinsel
|