mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-23 18:24:59 +00:00
6d80424908
svn-id: r48696
879 lines
21 KiB
C++
879 lines
21 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.
|
|
*
|
|
* Additional copyright for this file:
|
|
* Copyright (C) 1994-1998 Revolution Software Ltd.
|
|
*
|
|
* 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$
|
|
*/
|
|
|
|
// One feature still missing is the original's DipMusic() function which, as
|
|
// far as I can understand, softened the music volume when someone was
|
|
// speaking, but only (?) if the music was playing loudly at the time.
|
|
//
|
|
// All things considered, I think this is more bother than it's worth.
|
|
|
|
|
|
#include "common/file.h"
|
|
#include "common/system.h"
|
|
|
|
#include "sound/decoders/mp3.h"
|
|
#include "sound/decoders/vorbis.h"
|
|
#include "sound/decoders/flac.h"
|
|
#include "sound/decoders/wave.h"
|
|
#include "sound/decoders/vag.h"
|
|
#include "sound/rate.h"
|
|
|
|
#include "sword2/sword2.h"
|
|
#include "sword2/defs.h"
|
|
#include "sword2/header.h"
|
|
#include "sword2/resman.h"
|
|
#include "sword2/sound.h"
|
|
|
|
namespace Sword2 {
|
|
|
|
/**
|
|
* This class behaves like SeekableSubReadStream, except it remembers where the
|
|
* previous read() or seek() took it, so that it can continue from that point
|
|
* the next time. This is because we're frequently streaming two pieces of
|
|
* music from the same file.
|
|
*/
|
|
class SafeSubReadStream : public Common::SeekableSubReadStream {
|
|
protected:
|
|
uint32 _previousPos;
|
|
public:
|
|
SafeSubReadStream(SeekableReadStream *parentStream, uint32 begin, uint32 end);
|
|
virtual uint32 read(void *dataPtr, uint32 dataSize);
|
|
virtual bool seek(int32 offset, int whence = SEEK_SET);
|
|
};
|
|
|
|
SafeSubReadStream::SafeSubReadStream(SeekableReadStream *parentStream, uint32 begin, uint32 end)
|
|
: SeekableSubReadStream(parentStream, begin, end, DisposeAfterUse::NO) {
|
|
_previousPos = 0;
|
|
}
|
|
|
|
uint32 SafeSubReadStream::read(void *dataPtr, uint32 dataSize) {
|
|
uint32 result;
|
|
SeekableSubReadStream::seek(_previousPos);
|
|
result = SeekableSubReadStream::read(dataPtr, dataSize);
|
|
_previousPos = pos();
|
|
return result;
|
|
}
|
|
|
|
bool SafeSubReadStream::seek(int32 offset, int whence) {
|
|
bool result = SeekableSubReadStream::seek(offset, whence);
|
|
_previousPos = pos();
|
|
return result;
|
|
}
|
|
|
|
static Audio::AudioStream *makeCLUStream(Common::File *fp, int size);
|
|
static Audio::AudioStream *makePSXCLUStream(Common::File *fp, int size);
|
|
|
|
static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, int cd, uint32 id, uint32 *numSamples) {
|
|
bool alreadyOpen;
|
|
|
|
if (!fh->file.isOpen()) {
|
|
alreadyOpen = false;
|
|
|
|
struct {
|
|
const char *ext;
|
|
int mode;
|
|
} file_types[] = {
|
|
#ifdef USE_FLAC
|
|
{ "clf", kFLACMode },
|
|
#endif
|
|
#ifdef USE_VORBIS
|
|
{ "clg", kVorbisMode },
|
|
#endif
|
|
#ifdef USE_MAD
|
|
{ "cl3", kMP3Mode },
|
|
#endif
|
|
{ "clu", kCLUMode }
|
|
};
|
|
|
|
int soundMode = 0;
|
|
char filename[20];
|
|
|
|
for (int i = 0; i < ARRAYSIZE(file_types); i++) {
|
|
sprintf(filename, "%s%d.%s", base, cd, file_types[i].ext);
|
|
if (Common::File::exists(filename)) {
|
|
soundMode = file_types[i].mode;
|
|
break;
|
|
}
|
|
|
|
sprintf(filename, "%s.%s", base, file_types[i].ext);
|
|
if (Common::File::exists(filename)) {
|
|
soundMode = file_types[i].mode;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (soundMode == 0)
|
|
return NULL;
|
|
|
|
fh->file.open(filename);
|
|
fh->fileType = soundMode;
|
|
if (!fh->file.isOpen()) {
|
|
warning("BS2 getAudioStream: Failed opening file '%s'", filename);
|
|
return NULL;
|
|
}
|
|
if (fh->fileSize != fh->file.size()) {
|
|
if (fh->idxTab) {
|
|
free(fh->idxTab);
|
|
fh->idxTab = NULL;
|
|
}
|
|
}
|
|
} else
|
|
alreadyOpen = true;
|
|
|
|
uint32 entrySize = (fh->fileType == kCLUMode) ? 2 : 3;
|
|
|
|
if (!fh->idxTab) {
|
|
fh->file.seek(0);
|
|
fh->idxLen = fh->file.readUint32LE();
|
|
fh->file.seek(entrySize * 4);
|
|
|
|
fh->idxTab = (uint32*)malloc(fh->idxLen * 3 * sizeof(uint32));
|
|
for (uint32 cnt = 0; cnt < fh->idxLen; cnt++) {
|
|
fh->idxTab[cnt * 3 + 0] = fh->file.readUint32LE();
|
|
fh->idxTab[cnt * 3 + 1] = fh->file.readUint32LE();
|
|
if (fh->fileType == kCLUMode) {
|
|
fh->idxTab[cnt * 3 + 2] = fh->idxTab[cnt * 3 + 1];
|
|
fh->idxTab[cnt * 3 + 1]--;
|
|
} else
|
|
fh->idxTab[cnt * 3 + 2] = fh->file.readUint32LE();
|
|
}
|
|
}
|
|
|
|
// FIXME: In the forest maze on Zombie Island, the scripts will often
|
|
// try to play song 451, which doesn't exist. We could easily substitute
|
|
// another for it here, but which one? There are roughly 250 musical
|
|
// cues to choose from.
|
|
|
|
uint32 pos = fh->idxTab[id * 3 + 0];
|
|
uint32 len = fh->idxTab[id * 3 + 1];
|
|
uint32 enc_len = fh->idxTab[id * 3 + 2];
|
|
|
|
if (numSamples)
|
|
*numSamples = len;
|
|
|
|
if (!pos || !len) {
|
|
// We couldn't find the sound. Possibly as a result of a bad
|
|
// installation (e.g. using the music file from CD 2 as the
|
|
// first music file). Don't close the file if it was already
|
|
// open though, because something is playing from it.
|
|
warning("getAudioStream: Could not find %s ID %d! Possibly the wrong file", base, id);
|
|
if (!alreadyOpen)
|
|
fh->file.close();
|
|
return NULL;
|
|
}
|
|
|
|
fh->file.seek(pos, SEEK_SET);
|
|
|
|
switch (fh->fileType) {
|
|
case kCLUMode:
|
|
if (Sword2Engine::isPsx())
|
|
return makePSXCLUStream(&fh->file, enc_len);
|
|
else
|
|
return makeCLUStream(&fh->file, enc_len);
|
|
#ifdef USE_MAD
|
|
case kMP3Mode: {
|
|
SafeSubReadStream *tmp = new SafeSubReadStream(&fh->file, pos, pos + enc_len);
|
|
return Audio::makeMP3Stream(tmp, DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
#ifdef USE_VORBIS
|
|
case kVorbisMode: {
|
|
SafeSubReadStream *tmp = new SafeSubReadStream(&fh->file, pos, pos + enc_len);
|
|
return Audio::makeVorbisStream(tmp, DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
#ifdef USE_FLAC
|
|
case kFLACMode: {
|
|
SafeSubReadStream *tmp = new SafeSubReadStream(&fh->file, pos, pos + enc_len);
|
|
return Audio::makeFLACStream(tmp, DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Custom AudioStream class to handle Broken Sword 2's audio compression.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#define GetCompressedShift(n) (((n) >> 4) & 0x0F)
|
|
#define GetCompressedSign(n) ((n) & 0x08)
|
|
#define GetCompressedAmplitude(n) ((n) & 0x07)
|
|
|
|
CLUInputStream::CLUInputStream(Common::File *file, int size)
|
|
: _file(file), _firstTime(true), _bufferEnd(_outbuf + BUFFER_SIZE) {
|
|
|
|
// Determine the end position.
|
|
_file_pos = _file->pos();
|
|
_end_pos = _file_pos + size;
|
|
|
|
// Read in initial data
|
|
refill();
|
|
}
|
|
|
|
CLUInputStream::~CLUInputStream() {
|
|
}
|
|
|
|
int CLUInputStream::readBuffer(int16 *buffer, const int numSamples) {
|
|
int samples = 0;
|
|
while (samples < numSamples && !eosIntern()) {
|
|
const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
|
|
memcpy(buffer, _pos, len * 2);
|
|
buffer += len;
|
|
_pos += len;
|
|
samples += len;
|
|
if (_pos >= _bufferEnd) {
|
|
refill();
|
|
}
|
|
}
|
|
return samples;
|
|
}
|
|
|
|
void CLUInputStream::refill() {
|
|
byte *in = _inbuf;
|
|
int16 *out = _outbuf;
|
|
|
|
_file->seek(_file_pos, SEEK_SET);
|
|
|
|
uint len_left = _file->read(in, MIN((uint32)BUFFER_SIZE, _end_pos - _file->pos()));
|
|
|
|
_file_pos = _file->pos();
|
|
|
|
while (len_left > 0) {
|
|
uint16 sample;
|
|
|
|
if (_firstTime) {
|
|
_firstTime = false;
|
|
_prev = READ_LE_UINT16(in);
|
|
sample = _prev;
|
|
len_left -= 2;
|
|
in += 2;
|
|
} else {
|
|
uint16 delta = GetCompressedAmplitude(*in) << GetCompressedShift(*in);
|
|
if (GetCompressedSign(*in))
|
|
sample = _prev - delta;
|
|
else
|
|
sample = _prev + delta;
|
|
|
|
_prev = sample;
|
|
len_left--;
|
|
in++;
|
|
}
|
|
|
|
*out++ = (int16)sample;
|
|
}
|
|
|
|
_pos = _outbuf;
|
|
_bufferEnd = out;
|
|
}
|
|
|
|
Audio::AudioStream *makeCLUStream(Common::File *file, int size) {
|
|
return new CLUInputStream(file, size);
|
|
}
|
|
|
|
Audio::AudioStream *makePSXCLUStream(Common::File *file, int size) {
|
|
|
|
// Buffer audio file data, and ask MemoryReadStream to dispose of it
|
|
// when not needed anymore.
|
|
|
|
byte *buffer = (byte *)malloc(size);
|
|
file->read(buffer, size);
|
|
return Audio::makeVagStream(new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Another custom AudioStream class, to wrap around the various AudioStream
|
|
// classes used for music decompression, and to add looping, fading, etc.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// The length of a fade-in/out, in milliseconds.
|
|
#define FADE_LENGTH 3000
|
|
|
|
MusicInputStream::MusicInputStream(int cd, SoundFileHandle *fh, uint32 musicId, bool looping) {
|
|
_cd = cd;
|
|
_fh = fh;
|
|
_musicId = musicId;
|
|
_looping = looping;
|
|
|
|
_bufferEnd = _buffer + BUFFER_SIZE;
|
|
_remove = false;
|
|
_fading = 0;
|
|
|
|
_decoder = getAudioStream(_fh, "music", _cd, _musicId, &_numSamples);
|
|
if (_decoder) {
|
|
_samplesLeft = _numSamples;
|
|
_fadeSamples = (getRate() * FADE_LENGTH) / 1000;
|
|
fadeUp();
|
|
|
|
// Read in initial data
|
|
refill();
|
|
}
|
|
}
|
|
|
|
MusicInputStream::~MusicInputStream() {
|
|
delete _decoder;
|
|
_decoder = NULL;
|
|
}
|
|
|
|
int MusicInputStream::readBuffer(int16 *buffer, const int numSamples) {
|
|
if (!_decoder)
|
|
return 0;
|
|
|
|
int samples = 0;
|
|
while (samples < numSamples && !eosIntern()) {
|
|
const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
|
|
memcpy(buffer, _pos, len * 2);
|
|
buffer += len;
|
|
_pos += len;
|
|
samples += len;
|
|
if (_pos >= _bufferEnd) {
|
|
refill();
|
|
}
|
|
}
|
|
return samples;
|
|
}
|
|
|
|
void MusicInputStream::refill() {
|
|
int16 *buf = _buffer;
|
|
uint32 numSamples = 0;
|
|
uint32 len_left;
|
|
bool endFade = false;
|
|
|
|
len_left = BUFFER_SIZE;
|
|
|
|
if (_fading > 0 && (uint32)_fading < len_left)
|
|
len_left = _fading;
|
|
|
|
if (_samplesLeft < len_left)
|
|
len_left = _samplesLeft;
|
|
|
|
if (!_looping) {
|
|
// Non-looping music is faded out at the end. If this fade
|
|
// out would have started somewhere within the len_left samples
|
|
// to read, we only read up to that point. This way, we can
|
|
// treat this fade as any other.
|
|
|
|
if (!_fading) {
|
|
uint32 currentlyAt = _numSamples - _samplesLeft;
|
|
uint32 fadeOutAt = _numSamples - _fadeSamples;
|
|
uint32 readTo = currentlyAt + len_left;
|
|
|
|
if (fadeOutAt == currentlyAt)
|
|
fadeDown();
|
|
else if (fadeOutAt > currentlyAt && fadeOutAt <= readTo) {
|
|
len_left = fadeOutAt - currentlyAt;
|
|
endFade = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
int desired = len_left - numSamples;
|
|
int len = _decoder->readBuffer(buf, desired);
|
|
|
|
// Shouldn't happen, but if it does it could cause an infinite loop.
|
|
// Of course there were bugs that caused it to happen several times
|
|
// during development. :-)
|
|
|
|
if (len < desired) {
|
|
warning("Expected %d samples, but got %d", desired, len);
|
|
_samplesLeft = len;
|
|
}
|
|
|
|
buf += len;
|
|
numSamples += len;
|
|
len_left -= len;
|
|
_samplesLeft -= len;
|
|
|
|
int16 *ptr;
|
|
|
|
if (_fading > 0) {
|
|
// Fade down
|
|
for (ptr = _buffer; ptr < buf; ptr++) {
|
|
if (_fading > 0) {
|
|
_fading--;
|
|
*ptr = (*ptr * _fading) / _fadeSamples;
|
|
}
|
|
if (_fading == 0) {
|
|
_looping = false;
|
|
_remove = true;
|
|
*ptr = 0;
|
|
}
|
|
}
|
|
} else if (_fading < 0) {
|
|
// Fade up
|
|
for (ptr = _buffer; ptr < buf; ptr++) {
|
|
_fading--;
|
|
*ptr = -(*ptr * _fading) / _fadeSamples;
|
|
if (_fading <= -_fadeSamples) {
|
|
_fading = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (endFade)
|
|
fadeDown();
|
|
|
|
if (!_samplesLeft) {
|
|
if (_looping) {
|
|
delete _decoder;
|
|
_decoder = getAudioStream(_fh, "music", _cd, _musicId, &_numSamples);
|
|
_samplesLeft = _numSamples;
|
|
} else
|
|
_remove = true;
|
|
}
|
|
|
|
_pos = _buffer;
|
|
_bufferEnd = buf;
|
|
}
|
|
|
|
void MusicInputStream::fadeUp() {
|
|
if (_fading > 0)
|
|
_fading = -_fading;
|
|
else if (_fading == 0)
|
|
_fading = -1;
|
|
}
|
|
|
|
void MusicInputStream::fadeDown() {
|
|
if (_fading < 0)
|
|
_fading = -_fading;
|
|
else if (_fading == 0)
|
|
_fading = _fadeSamples;
|
|
}
|
|
|
|
bool MusicInputStream::readyToRemove() {
|
|
return _remove;
|
|
}
|
|
|
|
int32 MusicInputStream::getTimeRemaining() {
|
|
// This is far from exact, but it doesn't have to be.
|
|
return (_samplesLeft + BUFFER_SIZE) / getRate();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Main sound class
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// AudioStream API
|
|
|
|
int Sound::readBuffer(int16 *buffer, const int numSamples) {
|
|
Common::StackLock lock(_mutex);
|
|
int i;
|
|
|
|
if (_musicPaused)
|
|
return 0;
|
|
|
|
for (i = 0; i < MAXMUS; i++) {
|
|
if (_music[i] && _music[i]->readyToRemove()) {
|
|
delete _music[i];
|
|
_music[i] = NULL;
|
|
}
|
|
}
|
|
|
|
memset(buffer, 0, 2 * numSamples);
|
|
|
|
if (!_mixBuffer || numSamples > _mixBufferLen) {
|
|
if (_mixBuffer)
|
|
_mixBuffer = (int16 *)realloc(_mixBuffer, 2 * numSamples);
|
|
else
|
|
_mixBuffer = (int16 *)malloc(2 * numSamples);
|
|
|
|
_mixBufferLen = numSamples;
|
|
}
|
|
|
|
if (!_mixBuffer)
|
|
return 0;
|
|
|
|
for (i = 0; i < MAXMUS; i++) {
|
|
if (!_music[i])
|
|
continue;
|
|
|
|
int len = _music[i]->readBuffer(_mixBuffer, numSamples);
|
|
|
|
if (!_musicMuted) {
|
|
for (int j = 0; j < len; j++) {
|
|
Audio::clampedAdd(buffer[j], _mixBuffer[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool inUse[MAXMUS];
|
|
|
|
for (i = 0; i < MAXMUS; i++)
|
|
inUse[i] = false;
|
|
|
|
for (i = 0; i < MAXMUS; i++) {
|
|
if (_music[i]) {
|
|
if (_music[i]->getCD() == 1)
|
|
inUse[0] = true;
|
|
else
|
|
inUse[1] = true;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < MAXMUS; i++) {
|
|
if (!inUse[i] && !_musicFile[i].inUse && _musicFile[i].file.isOpen())
|
|
_musicFile[i].file.close();
|
|
}
|
|
|
|
return numSamples;
|
|
}
|
|
|
|
bool Sound::endOfData() const {
|
|
// The music never stops. It just goes quiet.
|
|
return false;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// MUSIC
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Stops the music dead in its tracks. Any music that is currently being
|
|
* streamed is paused.
|
|
*/
|
|
|
|
void Sound::pauseMusic() {
|
|
Common::StackLock lock(_mutex);
|
|
|
|
_musicPaused = true;
|
|
}
|
|
|
|
/**
|
|
* Restarts the music from where it was stopped.
|
|
*/
|
|
|
|
void Sound::unpauseMusic() {
|
|
Common::StackLock lock(_mutex);
|
|
|
|
_musicPaused = false;
|
|
}
|
|
|
|
/**
|
|
* Fades out and stops the music.
|
|
*/
|
|
|
|
void Sound::stopMusic(bool immediately) {
|
|
Common::StackLock lock(_mutex);
|
|
|
|
_loopingMusicId = 0;
|
|
|
|
for (int i = 0; i < MAXMUS; i++) {
|
|
if (_music[i]) {
|
|
if (immediately) {
|
|
delete _music[i];
|
|
_music[i] = NULL;
|
|
} else
|
|
_music[i]->fadeDown();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Streams music from a cluster file.
|
|
* @param musicId the id of the music to stream
|
|
* @param loop true if the music is to loop back to the start
|
|
* @return RD_OK or an error code
|
|
*/
|
|
int32 Sound::streamCompMusic(uint32 musicId, bool loop) {
|
|
Common::StackLock lock(_mutex);
|
|
|
|
int cd = _vm->_resman->getCD();
|
|
|
|
if (loop)
|
|
_loopingMusicId = musicId;
|
|
else
|
|
_loopingMusicId = 0;
|
|
|
|
int primary = -1;
|
|
int secondary = -1;
|
|
|
|
// If both music streams are active, one of them will have to go.
|
|
|
|
if (_music[0] && _music[1]) {
|
|
int32 fade0 = _music[0]->isFading();
|
|
int32 fade1 = _music[1]->isFading();
|
|
|
|
if (!fade0 && !fade1) {
|
|
// Neither is fading. This shouldn't happen, so just
|
|
// pick one and be done with it.
|
|
primary = 0;
|
|
} else if (fade0 && !fade1) {
|
|
// Stream 0 is fading, so pick that one.
|
|
primary = 0;
|
|
} else if (!fade0 && fade1) {
|
|
// Stream 1 is fading, so pick that one.
|
|
primary = 1;
|
|
} else {
|
|
// Both streams are fading. Pick the one that is
|
|
// closest to silent.
|
|
if (ABS(fade0) < ABS(fade1))
|
|
primary = 0;
|
|
else
|
|
primary = 1;
|
|
}
|
|
|
|
delete _music[primary];
|
|
_music[primary] = NULL;
|
|
}
|
|
|
|
// Pick the available music stream. If no music is playing it doesn't
|
|
// matter which we use.
|
|
|
|
if (_music[0] || _music[1]) {
|
|
if (_music[0]) {
|
|
primary = 1;
|
|
secondary = 0;
|
|
} else {
|
|
primary = 0;
|
|
secondary = 1;
|
|
}
|
|
} else
|
|
primary = 0;
|
|
|
|
// Don't start streaming if the volume is off.
|
|
if (isMusicMute()) {
|
|
return RD_OK;
|
|
}
|
|
|
|
if (secondary != -1)
|
|
_music[secondary]->fadeDown();
|
|
SoundFileHandle *fh = (cd == 1) ? &_musicFile[0] : &_musicFile[1];
|
|
fh->inUse = true;
|
|
|
|
MusicInputStream *tmp = new MusicInputStream(cd, fh, musicId, loop);
|
|
|
|
if (tmp->isReady()) {
|
|
_music[primary] = tmp;
|
|
fh->inUse = false;
|
|
return RD_OK;
|
|
} else {
|
|
fh->inUse = false;
|
|
delete tmp;
|
|
return RDERR_INVALIDFILENAME;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return the time left for the current music, in seconds.
|
|
*/
|
|
|
|
int32 Sound::musicTimeRemaining() {
|
|
Common::StackLock lock(_mutex);
|
|
|
|
for (int i = 0; i < MAXMUS; i++) {
|
|
if (_music[i] && _music[i]->isFading() <= 0)
|
|
return _music[i]->getTimeRemaining();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SPEECH
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Mutes/Unmutes the speech.
|
|
* @param mute If mute is false, restore the volume to the last set master
|
|
* level. Otherwise the speech is muted (volume 0).
|
|
*/
|
|
|
|
void Sound::muteSpeech(bool mute) {
|
|
_speechMuted = mute;
|
|
|
|
if (_vm->_mixer->isSoundHandleActive(_soundHandleSpeech)) {
|
|
uint volume = mute ? 0 : Audio::Mixer::kMaxChannelVolume;
|
|
|
|
_vm->_mixer->setChannelVolume(_soundHandleSpeech, volume);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops the speech dead in its tracks.
|
|
*/
|
|
|
|
void Sound::pauseSpeech() {
|
|
if (!_speechPaused) {
|
|
_speechPaused = true;
|
|
_vm->_mixer->pauseHandle(_soundHandleSpeech, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restarts the speech from where it was stopped.
|
|
*/
|
|
|
|
void Sound::unpauseSpeech() {
|
|
if (_speechPaused) {
|
|
_speechPaused = false;
|
|
_vm->_mixer->pauseHandle(_soundHandleSpeech, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops the speech from playing.
|
|
*/
|
|
|
|
int32 Sound::stopSpeech() {
|
|
if (_vm->_mixer->isSoundHandleActive(_soundHandleSpeech)) {
|
|
_vm->_mixer->stopHandle(_soundHandleSpeech);
|
|
return RD_OK;
|
|
}
|
|
|
|
return RDERR_SPEECHNOTPLAYING;
|
|
}
|
|
|
|
/**
|
|
* @return Either RDSE_SAMPLEPLAYING or RDSE_SAMPLEFINISHED
|
|
*/
|
|
|
|
int32 Sound::getSpeechStatus() {
|
|
return _vm->_mixer->isSoundHandleActive(_soundHandleSpeech) ? RDSE_SAMPLEPLAYING : RDSE_SAMPLEFINISHED;
|
|
}
|
|
|
|
/**
|
|
* Returns either RDSE_QUIET or RDSE_SPEAKING
|
|
*/
|
|
|
|
int32 Sound::amISpeaking() {
|
|
if (!_speechMuted && !_speechPaused && _vm->_mixer->isSoundHandleActive(_soundHandleSpeech))
|
|
return RDSE_SPEAKING;
|
|
|
|
return RDSE_QUIET;
|
|
}
|
|
|
|
/**
|
|
* This function loads, decompresses and plays a line of speech. An error
|
|
* occurs if speech is already playing.
|
|
* @param speechId the text line id used to reference the speech
|
|
* @param vol volume, 0 (minimum) to 16 (maximum)
|
|
* @param pan panning, -16 (full left) to 16 (full right)
|
|
*/
|
|
|
|
int32 Sound::playCompSpeech(uint32 speechId, uint8 vol, int8 pan) {
|
|
if (_speechMuted)
|
|
return RD_OK;
|
|
|
|
if (getSpeechStatus() == RDERR_SPEECHPLAYING)
|
|
return RDERR_SPEECHPLAYING;
|
|
|
|
int cd = _vm->_resman->getCD();
|
|
SoundFileHandle *fh = (cd == 1) ? &_speechFile[0] : &_speechFile[1];
|
|
|
|
Audio::AudioStream *input = getAudioStream(fh, "speech", cd, speechId, NULL);
|
|
|
|
if (!input)
|
|
return RDERR_INVALIDID;
|
|
|
|
// Modify the volume according to the master volume
|
|
|
|
byte volume = _speechMuted ? 0 : vol * Audio::Mixer::kMaxChannelVolume / 16;
|
|
int8 p = (pan * 127) / 16;
|
|
|
|
if (isReverseStereo())
|
|
p = -p;
|
|
|
|
// Start the speech playing
|
|
_vm->_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_soundHandleSpeech, input, -1, volume, p);
|
|
return RD_OK;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SOUND EFFECTS
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Mutes/Unmutes the sound effects.
|
|
* @param mute If mute is false, restore the volume to the last set master
|
|
* level. Otherwise the sound effects are muted (volume 0).
|
|
*/
|
|
|
|
void Sound::muteFx(bool mute) {
|
|
_fxMuted = mute;
|
|
|
|
// Now update the volume of any fxs playing
|
|
for (int i = 0; i < FXQ_LENGTH; i++) {
|
|
if (_fxQueue[i].resource) {
|
|
_vm->_mixer->setChannelVolume(_fxQueue[i].handle, mute ? 0 : _fxQueue[i].volume);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the volume and pan of the sample which is currently playing
|
|
* @param id the id of the sample
|
|
* @param vol volume
|
|
* @param pan panning
|
|
*/
|
|
|
|
int32 Sound::setFxIdVolumePan(int32 id, int vol, int pan) {
|
|
if (!_fxQueue[id].resource)
|
|
return RDERR_FXNOTOPEN;
|
|
|
|
if (vol > 16)
|
|
vol = 16;
|
|
|
|
_fxQueue[id].volume = (vol * Audio::Mixer::kMaxChannelVolume) / 16;
|
|
|
|
if (pan != 255) {
|
|
if (isReverseStereo())
|
|
pan = -pan;
|
|
_fxQueue[id].pan = (pan * 127) / 16;
|
|
}
|
|
|
|
if (!_fxMuted && _vm->_mixer->isSoundHandleActive(_fxQueue[id].handle)) {
|
|
_vm->_mixer->setChannelVolume(_fxQueue[id].handle, _fxQueue[id].volume);
|
|
if (pan != -1)
|
|
_vm->_mixer->setChannelBalance(_fxQueue[id].handle, _fxQueue[id].pan);
|
|
}
|
|
|
|
return RD_OK;
|
|
}
|
|
|
|
void Sound::pauseFx() {
|
|
if (!_fxPaused) {
|
|
for (int i = 0; i < FXQ_LENGTH; i++) {
|
|
if (_fxQueue[i].resource)
|
|
_vm->_mixer->pauseHandle(_fxQueue[i].handle, true);
|
|
}
|
|
_fxPaused = true;
|
|
}
|
|
}
|
|
|
|
void Sound::unpauseFx() {
|
|
if (_fxPaused) {
|
|
for (int i = 0; i < FXQ_LENGTH; i++)
|
|
if (_fxQueue[i].resource)
|
|
_vm->_mixer->pauseHandle(_fxQueue[i].handle, false);
|
|
_fxPaused = false;
|
|
}
|
|
}
|
|
|
|
} // End of namespace Sword2
|