2004-01-06 13:44:17 +00:00
|
|
|
/* Copyright (C) 1994-2004 Revolution Software Ltd
|
2003-07-28 01:47:41 +00:00
|
|
|
*
|
|
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*
|
|
|
|
* $Header$
|
|
|
|
*/
|
|
|
|
|
2004-08-28 14:50:44 +00:00
|
|
|
// 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.
|
2003-09-26 14:19:03 +00:00
|
|
|
//
|
2004-08-28 14:50:44 +00:00
|
|
|
// All things considered, I think this is more bother than it's worth.
|
2003-09-26 14:19:03 +00:00
|
|
|
|
2003-11-16 14:18:29 +00:00
|
|
|
#include "common/stdafx.h"
|
|
|
|
#include "common/file.h"
|
2003-12-24 00:25:18 +00:00
|
|
|
#include "sound/rate.h"
|
2004-08-22 14:28:11 +00:00
|
|
|
#include "sound/mp3.h"
|
|
|
|
#include "sound/vorbis.h"
|
|
|
|
#include "sound/flac.h"
|
2004-02-05 14:19:07 +00:00
|
|
|
#include "sword2/sword2.h"
|
2004-08-22 14:28:11 +00:00
|
|
|
#include "sword2/resman.h"
|
2004-02-05 14:19:07 +00:00
|
|
|
#include "sword2/driver/d_draw.h"
|
|
|
|
#include "sword2/driver/d_sound.h"
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2003-10-04 00:52:27 +00:00
|
|
|
namespace Sword2 {
|
|
|
|
|
2003-09-25 06:11:07 +00:00
|
|
|
static File fpMus;
|
2003-09-21 14:26:25 +00:00
|
|
|
|
2004-08-28 14:50:44 +00:00
|
|
|
static void premix_proc(void *param, int16 *data, uint len) {
|
|
|
|
((Sound *) param)->streamMusic(data, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Custom AudioStream class to handle Broken Sword II's audio compression.
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#define BUFFER_SIZE 4096
|
|
|
|
|
2003-09-25 10:02:52 +00:00
|
|
|
#define GetCompressedShift(n) ((n) >> 4)
|
|
|
|
#define GetCompressedSign(n) (((n) >> 3) & 1)
|
|
|
|
#define GetCompressedAmplitude(n) ((n) & 7)
|
2003-07-29 12:34:46 +00:00
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
class CLUInputStream : public AudioStream {
|
2004-08-28 14:50:44 +00:00
|
|
|
private:
|
2004-08-25 06:55:15 +00:00
|
|
|
File *_file;
|
2004-08-26 06:59:15 +00:00
|
|
|
uint32 _end_pos;
|
2004-08-25 06:55:15 +00:00
|
|
|
int16 _outbuf[BUFFER_SIZE];
|
|
|
|
byte _inbuf[BUFFER_SIZE];
|
|
|
|
const int16 *_bufferEnd;
|
|
|
|
const int16 *_pos;
|
|
|
|
|
|
|
|
uint16 _prev;
|
|
|
|
|
|
|
|
void refill();
|
|
|
|
inline bool eosIntern() const;
|
|
|
|
public:
|
|
|
|
CLUInputStream(File *file, int duration);
|
|
|
|
~CLUInputStream();
|
|
|
|
|
|
|
|
int readBuffer(int16 *buffer, const int numSamples);
|
|
|
|
|
|
|
|
int16 read();
|
|
|
|
bool endOfData() const { return eosIntern(); }
|
|
|
|
bool isStereo() const { return false; }
|
|
|
|
int getRate() const { return 22050; }
|
|
|
|
};
|
|
|
|
|
|
|
|
CLUInputStream::CLUInputStream(File *file, int size)
|
|
|
|
: _file(file), _bufferEnd(_outbuf + BUFFER_SIZE) {
|
|
|
|
|
|
|
|
_file->incRef();
|
|
|
|
|
|
|
|
// Determine the end position.
|
|
|
|
_end_pos = file->pos() + size;
|
|
|
|
|
|
|
|
// Read in initial data
|
2004-08-27 08:31:33 +00:00
|
|
|
_prev = _file->readUint16LE();
|
2004-08-25 06:55:15 +00:00
|
|
|
refill();
|
|
|
|
}
|
|
|
|
|
|
|
|
CLUInputStream::~CLUInputStream() {
|
|
|
|
_file->decRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline int16 CLUInputStream::read() {
|
|
|
|
assert(!eosIntern());
|
|
|
|
|
|
|
|
int16 sample = *_pos++;
|
|
|
|
if (_pos >= _bufferEnd) {
|
|
|
|
refill();
|
|
|
|
}
|
|
|
|
return sample;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline bool CLUInputStream::eosIntern() const {
|
|
|
|
return _pos >= _bufferEnd;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2004-08-26 06:59:15 +00:00
|
|
|
uint len_left = _file->read(in, MIN((uint32) BUFFER_SIZE, _end_pos - _file->pos()));
|
2004-08-25 06:55:15 +00:00
|
|
|
|
|
|
|
while (len_left > 0) {
|
|
|
|
uint16 delta = GetCompressedAmplitude(*in) << GetCompressedShift(*in);
|
|
|
|
uint16 sample;
|
|
|
|
|
|
|
|
if (GetCompressedSign(*in))
|
|
|
|
sample = _prev - delta;
|
|
|
|
else
|
|
|
|
sample = _prev + delta;
|
|
|
|
|
|
|
|
_prev = sample;
|
|
|
|
*out++ = sample;
|
|
|
|
*in++;
|
|
|
|
len_left--;
|
|
|
|
}
|
|
|
|
|
|
|
|
_pos = _outbuf;
|
|
|
|
_bufferEnd = out;
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioStream *makeCLUStream(File *file, int size) {
|
|
|
|
assert(size >= 2);
|
|
|
|
return new CLUInputStream(file, size);
|
|
|
|
}
|
|
|
|
|
2004-08-28 14:50:44 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Main sound class
|
|
|
|
// ----------------------------------------------------------------------------
|
2004-01-03 11:24:39 +00:00
|
|
|
|
|
|
|
Sound::Sound(Sword2Engine *vm) {
|
|
|
|
_vm = vm;
|
2004-02-28 12:58:13 +00:00
|
|
|
_mutex = _vm->_system->createMutex();
|
2004-01-03 11:24:39 +00:00
|
|
|
|
|
|
|
memset(_fx, 0, sizeof(_fx));
|
|
|
|
|
|
|
|
_soundOn = true;
|
|
|
|
|
|
|
|
_speechStatus = false;
|
|
|
|
_speechPaused = false;
|
|
|
|
_speechMuted = false;
|
|
|
|
_speechVol = 14;
|
|
|
|
|
|
|
|
_fxPaused = false;
|
|
|
|
_fxMuted = false;
|
|
|
|
_fxVol = 14;
|
|
|
|
|
|
|
|
_musicVol = 16;
|
|
|
|
_musicMuted = false;
|
|
|
|
|
2004-07-17 14:00:07 +00:00
|
|
|
for (int i = 0; i < MAXMUS; i++)
|
|
|
|
_music[i]._converter = makeRateConverter(_music[i].getRate(), _vm->_mixer->getOutputRate(), _music[i].isStereo(), false);
|
2004-01-03 11:24:39 +00:00
|
|
|
|
|
|
|
_vm->_mixer->setupPremix(premix_proc, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
Sound::~Sound() {
|
2004-07-17 14:00:07 +00:00
|
|
|
int i;
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
_vm->_mixer->setupPremix(0, 0);
|
2004-07-17 14:00:07 +00:00
|
|
|
|
|
|
|
for (i = 0; i < MAXMUS; i++)
|
|
|
|
delete _music[i]._converter;
|
|
|
|
|
|
|
|
for (i = 0; i < MAXFX; i++)
|
2004-01-07 07:42:00 +00:00
|
|
|
stopFxHandle(i);
|
2004-07-17 14:00:07 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (_mutex)
|
2004-02-28 12:58:13 +00:00
|
|
|
_vm->_system->deleteMutex(_mutex);
|
2004-01-03 11:24:39 +00:00
|
|
|
}
|
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
// FIXME: Merge openSoundFile() and getAudioStream() once compressed music has
|
|
|
|
// been implemented?
|
|
|
|
|
2004-08-22 14:28:11 +00:00
|
|
|
int Sound::openSoundFile(File *fp, const char *base) {
|
|
|
|
struct {
|
|
|
|
const char *ext;
|
|
|
|
int mode;
|
|
|
|
} file_types[] = {
|
|
|
|
#ifdef USE_MAD
|
|
|
|
{ "cl3", kMP3Mode },
|
|
|
|
#endif
|
|
|
|
#ifdef USE_VORBIS
|
|
|
|
{ "clg", kVorbisMode },
|
|
|
|
#endif
|
|
|
|
#ifdef USE_FLAC
|
|
|
|
{ "clf", kFlacMode },
|
|
|
|
#endif
|
2004-08-25 06:55:15 +00:00
|
|
|
{ "clu", kCLUMode }
|
2004-08-22 14:28:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
char filename[20];
|
|
|
|
int cd = _vm->_resman->whichCd();
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAYSIZE(file_types); i++) {
|
2004-08-28 14:50:44 +00:00
|
|
|
File f;
|
|
|
|
|
2004-08-22 14:28:11 +00:00
|
|
|
sprintf(filename, "%s%d.%s", base, cd, file_types[i].ext);
|
2004-08-28 14:50:44 +00:00
|
|
|
if (f.open(filename))
|
|
|
|
break;
|
2004-08-22 14:28:11 +00:00
|
|
|
|
|
|
|
sprintf(filename, "%s.%s", base, file_types[i].ext);
|
2004-08-28 14:50:44 +00:00
|
|
|
if (f.open(filename))
|
|
|
|
break;
|
2004-08-22 14:28:11 +00:00
|
|
|
}
|
|
|
|
|
2004-08-28 14:50:44 +00:00
|
|
|
if (i == ARRAYSIZE(file_types))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// The assumption here is that a sound file is closed when the sound
|
|
|
|
// finishes, and we never play sounds from two different files at the
|
|
|
|
// same time. Thus, if the file is already open it's the correct file,
|
|
|
|
// and the loop above was just needed to figure out the compression.
|
|
|
|
//
|
|
|
|
// This is to avoid having two file handles open to the same file at
|
|
|
|
// the same time. There was some speculation that some of our target
|
|
|
|
// systems may have trouble with that.
|
|
|
|
|
|
|
|
if (!fp->isOpen())
|
|
|
|
fp->open(filename);
|
|
|
|
|
|
|
|
return file_types[i].mode;
|
2004-08-22 14:28:11 +00:00
|
|
|
}
|
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
AudioStream *Sound::getAudioStream(File *fp, const char *base, uint32 id, uint32 *numSamples) {
|
|
|
|
int soundMode = openSoundFile(fp, base);
|
|
|
|
|
|
|
|
if (!soundMode)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
fp->seek((id + 1) * ((soundMode == kCLUMode) ? 8 : 12), SEEK_SET);
|
|
|
|
|
|
|
|
uint32 pos = fp->readUint32LE();
|
|
|
|
uint32 len = fp->readUint32LE();
|
|
|
|
uint32 enc_len;
|
|
|
|
|
|
|
|
if (numSamples)
|
|
|
|
*numSamples = len;
|
|
|
|
|
|
|
|
if (soundMode != kCLUMode)
|
|
|
|
enc_len = fp->readUint32LE();
|
|
|
|
else
|
|
|
|
enc_len = len + 1;
|
|
|
|
|
|
|
|
if (!pos || !len) {
|
|
|
|
fp->close();
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
fp->seek(pos, SEEK_SET);
|
|
|
|
|
|
|
|
switch (soundMode) {
|
|
|
|
case kCLUMode:
|
|
|
|
return makeCLUStream(fp, enc_len);
|
|
|
|
#ifdef USE_MAD
|
|
|
|
case kMP3Mode:
|
|
|
|
return makeMP3Stream(fp, enc_len);
|
|
|
|
#endif
|
|
|
|
#ifdef USE_VORBIS
|
|
|
|
case kVorbisMode:
|
|
|
|
return makeVorbisStream(fp, enc_len);
|
|
|
|
#endif
|
|
|
|
#ifdef USE_FLAC
|
|
|
|
case kFlacMode:
|
|
|
|
return makeFlacStream(fp, enc_len);
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-06-12 09:53:45 +00:00
|
|
|
void Sound::streamMusic(int16 *data, uint len) {
|
2004-01-03 11:24:39 +00:00
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
|
|
|
|
if (!_soundOn)
|
|
|
|
return;
|
|
|
|
|
2004-06-12 09:53:45 +00:00
|
|
|
for (int i = 0; i < MAXMUS; i++) {
|
|
|
|
if (!_music[i]._streaming || _music[i]._paused)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
st_volume_t volume = _musicMuted ? 0 : _musicVolTable[_musicVol];
|
|
|
|
|
|
|
|
fpMus.seek(_music[i]._filePos, SEEK_SET);
|
2004-07-17 14:00:07 +00:00
|
|
|
_music[i]._converter->flow(_music[i], data, len, volume, volume);
|
2004-06-12 09:53:45 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (!_music[0]._streaming && !_music[1]._streaming && fpMus.isOpen())
|
|
|
|
fpMus.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function creates the pan table.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// FIXME: Could we use the FLAG_REVERSE_STEREO mixer flag instead?
|
|
|
|
|
|
|
|
void Sound::buildPanTable(bool reverse) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
|
|
_panTable[i] = -127 + i * 8;
|
|
|
|
_panTable[i + 17] = (i + 1) * 8 - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
_panTable[16] = 0;
|
|
|
|
|
|
|
|
if (reverse) {
|
|
|
|
for (i = 0; i < 33; i++)
|
|
|
|
_panTable[i] = -_panTable[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// MUSIC
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// All music is encoded at 22050 Hz so this means fading takes 3 seconds.
|
|
|
|
#define FADE_SAMPLES 66150
|
|
|
|
|
|
|
|
int32 Sound::_musicVolTable[17] = {
|
2003-09-26 14:19:03 +00:00
|
|
|
0, 15, 31, 47, 63, 79, 95, 111, 127,
|
|
|
|
143, 159, 175, 191, 207, 223, 239, 255
|
2003-08-31 20:26:21 +00:00
|
|
|
};
|
2003-09-21 14:26:25 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void MusicHandle::fadeDown(void) {
|
2004-01-14 18:33:30 +00:00
|
|
|
if (_streaming) {
|
|
|
|
if (_fading < 0)
|
|
|
|
_fading = -_fading;
|
|
|
|
else if (_fading == 0)
|
|
|
|
_fading = FADE_SAMPLES;
|
|
|
|
}
|
2004-01-03 11:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MusicHandle::fadeUp(void) {
|
2004-01-14 18:33:30 +00:00
|
|
|
if (_streaming) {
|
|
|
|
if (_fading > 0)
|
|
|
|
_fading = -_fading;
|
|
|
|
else if (_fading == 0)
|
|
|
|
_fading = -1;
|
|
|
|
}
|
2004-01-03 11:24:39 +00:00
|
|
|
}
|
|
|
|
|
2004-08-27 08:31:33 +00:00
|
|
|
int32 MusicHandle::play(uint32 musicId, bool looping) {
|
2004-01-03 11:24:39 +00:00
|
|
|
fpMus.seek((musicId + 1) * 8, SEEK_SET);
|
|
|
|
_fileStart = fpMus.readUint32LE();
|
|
|
|
|
|
|
|
uint32 len = fpMus.readUint32LE();
|
|
|
|
|
|
|
|
if (!_fileStart || !len)
|
|
|
|
return RDERR_INVALIDID;
|
|
|
|
|
|
|
|
_fileEnd = _fileStart + len;
|
|
|
|
_filePos = _fileStart;
|
|
|
|
_streaming = true;
|
|
|
|
_firstTime = true;
|
|
|
|
_looping = looping;
|
|
|
|
fadeUp();
|
|
|
|
return RD_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MusicHandle::stop(void) {
|
|
|
|
_streaming = false;
|
|
|
|
_looping = false;
|
|
|
|
_fading = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int MusicHandle::readBuffer(int16 *buffer, const int numSamples) {
|
2004-01-03 19:12:23 +00:00
|
|
|
assert(numSamples > 0);
|
2004-01-03 11:24:39 +00:00
|
|
|
int samples;
|
Ok, I'm stupid.
The initial sample is, indeed, two bytes, just like the rest of them, but
it really, really helps if you read it from the correct position in the
file.
After fixing that, it turned out that my changing of signedness of the
sample was also wrong. Funny how those two bugs almost cancelled each other
out. Almost.
I've made a few other changes as well, but they're just to clean things up
a bit. The credits music works for me, and I've played the game up to
arriving in Quaramonte, with no obvious music-related problems.
svn-id: r10412
2003-09-26 06:26:18 +00:00
|
|
|
|
2003-09-25 06:11:07 +00:00
|
|
|
// Assume the file handle has been correctly positioned already.
|
|
|
|
|
2004-01-03 19:12:23 +00:00
|
|
|
for (samples = 0; samples < numSamples && !endOfData(); samples++) {
|
|
|
|
int16 out;
|
|
|
|
if (_firstTime) {
|
|
|
|
_lastSample = fpMus.readUint16LE();
|
|
|
|
_filePos += 2;
|
|
|
|
_firstTime = false;
|
|
|
|
out = _lastSample;
|
|
|
|
} else {
|
|
|
|
uint8 in = fpMus.readByte();
|
|
|
|
uint16 delta = GetCompressedAmplitude(in) << GetCompressedShift(in);
|
|
|
|
|
|
|
|
if (GetCompressedSign(in))
|
|
|
|
out = _lastSample - delta;
|
|
|
|
else
|
|
|
|
out = _lastSample + delta;
|
|
|
|
|
|
|
|
_filePos++;
|
|
|
|
_lastSample = out;
|
|
|
|
|
|
|
|
if (_looping) {
|
|
|
|
if (_filePos >= _fileEnd) {
|
|
|
|
_firstTime = true;
|
|
|
|
_filePos = _fileStart;
|
|
|
|
fpMus.seek(_filePos, SEEK_SET);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Fade out at the end of the music, unless it already is.
|
|
|
|
if (_fileEnd - _filePos <= FADE_SAMPLES && _fading <= 0)
|
|
|
|
fadeDown();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_fading > 0) {
|
|
|
|
if (--_fading == 0) {
|
|
|
|
_streaming = false;
|
|
|
|
_looping = false;
|
|
|
|
}
|
|
|
|
out = (out * _fading) / FADE_SAMPLES;
|
|
|
|
} else if (_fading < 0) {
|
2004-01-14 08:06:56 +00:00
|
|
|
_fading--;
|
|
|
|
out = -(out * _fading) / FADE_SAMPLES;
|
2004-01-14 18:33:30 +00:00
|
|
|
if (_fading <= -FADE_SAMPLES)
|
2004-01-14 08:06:56 +00:00
|
|
|
_fading = 0;
|
2004-01-03 19:12:23 +00:00
|
|
|
}
|
Ok, I'm stupid.
The initial sample is, indeed, two bytes, just like the rest of them, but
it really, really helps if you read it from the correct position in the
file.
After fixing that, it turned out that my changing of signedness of the
sample was also wrong. Funny how those two bugs almost cancelled each other
out. Almost.
I've made a few other changes as well, but they're just to clean things up
a bit. The credits music works for me, and I've played the game up to
arriving in Quaramonte, with no obvious music-related problems.
svn-id: r10412
2003-09-26 06:26:18 +00:00
|
|
|
}
|
2003-09-25 06:11:07 +00:00
|
|
|
|
2004-01-03 19:12:23 +00:00
|
|
|
*buffer++ = out;
|
2003-09-25 06:11:07 +00:00
|
|
|
}
|
2003-09-16 07:11:29 +00:00
|
|
|
|
2004-01-03 19:12:23 +00:00
|
|
|
return samples;
|
2003-09-25 06:11:07 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
bool MusicHandle::endOfData(void) const {
|
2003-12-19 00:27:09 +00:00
|
|
|
return (!_streaming || _filePos >= _fileEnd);
|
2003-09-25 06:11:07 +00:00
|
|
|
}
|
|
|
|
|
2004-01-16 08:16:23 +00:00
|
|
|
/**
|
|
|
|
* Retrieve information about an in-memory WAV file.
|
|
|
|
* @param data The WAV data
|
|
|
|
* @param wavInfo Pointer to the WavInfo structure to fill with information.
|
|
|
|
* @return True if the data appears to be a WAV file, otherwise false.
|
|
|
|
*/
|
|
|
|
|
2004-04-23 07:02:11 +00:00
|
|
|
bool Sound::getWavInfo(byte *data, WavInfo *wavInfo) {
|
2004-02-15 14:22:54 +00:00
|
|
|
uint32 wavLength;
|
|
|
|
uint32 offset;
|
|
|
|
|
2004-01-16 08:16:23 +00:00
|
|
|
if (READ_UINT32(data) != MKID('RIFF')) {
|
|
|
|
warning("getWavInfo: No 'RIFF' header");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2004-02-15 14:22:54 +00:00
|
|
|
wavLength = READ_LE_UINT32(data + 4) + 8;
|
|
|
|
|
2004-01-16 08:16:23 +00:00
|
|
|
if (READ_UINT32(data + 8) != MKID('WAVE')) {
|
|
|
|
warning("getWavInfo: No 'WAVE' header");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (READ_UINT32(data + 12) != MKID('fmt ')) {
|
|
|
|
warning("getWavInfo: No 'fmt' header");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
wavInfo->channels = READ_LE_UINT16(data + 22);
|
|
|
|
wavInfo->rate = READ_LE_UINT16(data + 24);
|
|
|
|
|
2004-02-15 14:22:54 +00:00
|
|
|
offset = READ_LE_UINT32(data + 16) + 20;
|
2004-01-16 08:16:23 +00:00
|
|
|
|
2004-02-15 14:22:54 +00:00
|
|
|
// It's almost certainly a WAV file, but we still need to find its
|
|
|
|
// 'data' chunk.
|
|
|
|
|
|
|
|
while (READ_UINT32(data + offset) != MKID('data')) {
|
|
|
|
if (offset >= wavLength) {
|
|
|
|
warning("getWavInfo: Can't find 'data' chunk");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
offset += (READ_LE_UINT32(data + offset + 4) + 8);
|
2004-01-16 08:16:23 +00:00
|
|
|
}
|
|
|
|
|
2004-02-15 14:22:54 +00:00
|
|
|
wavInfo->samples = READ_LE_UINT32(data + offset + 4);
|
|
|
|
wavInfo->data = data + offset + 8;
|
2004-01-16 08:16:23 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
/**
|
|
|
|
* Mutes/Unmutes the music.
|
|
|
|
* @param mute If mute is false, restore the volume to the last set master
|
|
|
|
* level. Otherwise the music is muted (volume 0).
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Sound::muteMusic(bool mute) {
|
|
|
|
_musicMuted = mute;
|
2003-08-30 18:06:08 +00:00
|
|
|
}
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
/**
|
|
|
|
* @return the music's mute state, true if mute, false if not mute
|
|
|
|
*/
|
2003-09-04 10:58:55 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
bool Sound::isMusicMute(void) {
|
|
|
|
return _musicMuted;
|
|
|
|
}
|
2003-10-01 06:36:25 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
/**
|
|
|
|
* Set the volume of any future as well as playing music.
|
|
|
|
* @param volume volume, from 0 (silent) to 16 (max)
|
|
|
|
*/
|
2003-09-21 14:26:25 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::setMusicVolume(uint8 volume) {
|
|
|
|
if (volume > 16)
|
|
|
|
volume = 16;
|
2003-09-21 14:26:25 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
_musicVol = volume;
|
|
|
|
}
|
2003-09-25 06:11:07 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
/**
|
|
|
|
* @return the volume setting for music
|
|
|
|
*/
|
2003-09-25 06:11:07 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
uint8 Sound::getMusicVolume(void) {
|
|
|
|
return _musicVol;
|
2003-07-29 12:34:46 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
/**
|
|
|
|
* Stops the music dead in its tracks. Any music that is currently being
|
|
|
|
* streamed is paused.
|
|
|
|
*/
|
2003-09-04 10:58:55 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::pauseMusic(void) {
|
|
|
|
Common::StackLock lock(_mutex);
|
2003-08-31 10:38:32 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (_soundOn) {
|
|
|
|
for (int i = 0; i < MAXMUS; i++)
|
|
|
|
_music[i]._paused = true;
|
|
|
|
}
|
|
|
|
}
|
2003-09-16 07:11:29 +00:00
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
2004-01-03 11:24:39 +00:00
|
|
|
* Restarts the music from where it was stopped.
|
2003-09-27 17:00:15 +00:00
|
|
|
*/
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::unpauseMusic(void) {
|
|
|
|
Common::StackLock lock(_mutex);
|
2003-11-01 18:12:04 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (_soundOn) {
|
|
|
|
for (int i = 0; i < MAXMUS; i++)
|
|
|
|
_music[i]._paused = false;
|
2003-11-01 18:12:04 +00:00
|
|
|
}
|
2004-01-03 11:24:39 +00:00
|
|
|
}
|
2003-11-01 18:12:04 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
/**
|
|
|
|
* Fades out and stops the music.
|
|
|
|
*/
|
2003-11-01 18:12:04 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::stopMusic(void) {
|
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
|
|
|
|
for (int i = 0; i < MAXMUS; i++)
|
|
|
|
_music[i].fadeDown();
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
/**
|
|
|
|
* Save/Restore information about current music so that we can restore it
|
|
|
|
* after the credits.
|
|
|
|
*/
|
2003-09-27 16:10:43 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::saveMusicState(void) {
|
2003-10-04 11:50:21 +00:00
|
|
|
Common::StackLock lock(_mutex);
|
2003-09-27 16:10:43 +00:00
|
|
|
|
|
|
|
int saveStream;
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (_music[0]._streaming && _music[0]._fading <= 0) {
|
2003-09-27 16:10:43 +00:00
|
|
|
saveStream = 0;
|
2004-01-03 11:24:39 +00:00
|
|
|
} else if (_music[1]._streaming && _music[0]._fading <= 0) {
|
2003-09-27 16:10:43 +00:00
|
|
|
saveStream = 1;
|
|
|
|
} else {
|
2003-10-01 06:36:25 +00:00
|
|
|
_music[2]._streaming = false;
|
2003-12-31 14:10:42 +00:00
|
|
|
savedMusicFilename = NULL;
|
2003-09-27 16:10:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2003-10-01 06:36:25 +00:00
|
|
|
_music[2]._streaming = true;
|
|
|
|
_music[2]._fading = 0;
|
|
|
|
_music[2]._looping = _music[saveStream]._looping;
|
|
|
|
_music[2]._fileStart = _music[saveStream]._fileStart;
|
|
|
|
_music[2]._filePos = _music[saveStream]._filePos;
|
|
|
|
_music[2]._fileEnd = _music[saveStream]._fileEnd;
|
|
|
|
_music[2]._lastSample = _music[saveStream]._lastSample;
|
2003-12-31 14:05:53 +00:00
|
|
|
|
|
|
|
if (fpMus.isOpen())
|
|
|
|
savedMusicFilename = strdup(fpMus.name());
|
|
|
|
else
|
|
|
|
savedMusicFilename = NULL;
|
2003-09-27 16:10:43 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::restoreMusicState(void) {
|
2003-10-04 11:50:21 +00:00
|
|
|
Common::StackLock lock(_mutex);
|
2003-09-27 16:10:43 +00:00
|
|
|
|
|
|
|
// Fade out any music that happens to be playing
|
|
|
|
|
2004-01-14 18:33:30 +00:00
|
|
|
for (int i = 0; i < MAXMUS; i++)
|
|
|
|
_music[i].fadeDown();
|
2003-09-27 16:10:43 +00:00
|
|
|
|
2004-01-12 08:01:25 +00:00
|
|
|
if (!_music[2]._streaming)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int restoreStream;
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (!_music[0]._streaming)
|
2003-09-27 16:10:43 +00:00
|
|
|
restoreStream = 0;
|
2004-01-03 11:24:39 +00:00
|
|
|
else
|
2003-09-27 16:10:43 +00:00
|
|
|
restoreStream = 1;
|
|
|
|
|
2003-10-01 06:36:25 +00:00
|
|
|
_music[restoreStream]._streaming = true;
|
|
|
|
_music[restoreStream]._fading = 0;
|
|
|
|
_music[restoreStream]._looping = _music[2]._looping;
|
|
|
|
_music[restoreStream]._fileStart = _music[2]._fileStart;
|
|
|
|
_music[restoreStream]._filePos = _music[2]._filePos;
|
|
|
|
_music[restoreStream]._fileEnd = _music[2]._fileEnd;
|
|
|
|
_music[restoreStream]._lastSample = _music[2]._lastSample;
|
2004-01-03 11:24:39 +00:00
|
|
|
_music[restoreStream].fadeUp();
|
2003-12-31 14:05:53 +00:00
|
|
|
|
|
|
|
if (savedMusicFilename) {
|
|
|
|
if (fpMus.isOpen())
|
|
|
|
fpMus.close();
|
|
|
|
|
|
|
|
fpMus.open(savedMusicFilename);
|
|
|
|
free(savedMusicFilename);
|
|
|
|
savedMusicFilename = NULL;
|
|
|
|
}
|
2003-09-27 16:10:43 +00:00
|
|
|
}
|
|
|
|
|
2004-02-15 14:22:54 +00:00
|
|
|
void Sound::waitForLeadOut(void) {
|
|
|
|
int i = getFxIndex(-1);
|
2003-09-28 14:13:57 +00:00
|
|
|
|
2004-02-15 14:22:54 +00:00
|
|
|
if (i == MAXFX)
|
2003-09-28 14:13:57 +00:00
|
|
|
return;
|
|
|
|
|
2003-12-24 00:25:18 +00:00
|
|
|
while (_fx[i]._handle.isActive()) {
|
2003-11-16 14:18:29 +00:00
|
|
|
_vm->_graphics->updateDisplay();
|
|
|
|
_vm->_system->delay_msecs(30);
|
2003-09-28 14:13:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
/**
|
|
|
|
* Streams music from a cluster file.
|
|
|
|
* @param filename the file name of the music cluster file
|
|
|
|
* @param musicId the id of the music to stream
|
|
|
|
* @param looping true if the music is to loop back to the start
|
|
|
|
* @return RD_OK or an error code
|
|
|
|
*/
|
2003-09-16 07:11:29 +00:00
|
|
|
|
2004-08-27 08:31:33 +00:00
|
|
|
int32 Sound::streamCompMusic(uint32 musicId, bool looping) {
|
2004-01-03 11:24:39 +00:00
|
|
|
Common::StackLock lock(_mutex);
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
int32 primaryStream = -1;
|
|
|
|
int32 secondaryStream = -1;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-14 08:06:56 +00:00
|
|
|
// If both music streams are playing, one of them will have to go.
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (_music[0]._streaming && _music[1]._streaming) {
|
2004-03-13 12:05:01 +00:00
|
|
|
if (!_music[0]._fading && !_music[1]._fading) {
|
2004-01-14 08:06:56 +00:00
|
|
|
// None of them are fading. This shouldn't happen, so
|
|
|
|
// just pick one and be done with it.
|
2004-01-03 11:24:39 +00:00
|
|
|
primaryStream = 0;
|
2004-03-13 12:05:01 +00:00
|
|
|
} else if (_music[0]._fading && !_music[1]._fading) {
|
2004-01-14 08:06:56 +00:00
|
|
|
// Stream 0 is fading, so pick that one.
|
|
|
|
primaryStream = 0;
|
2004-03-13 12:05:01 +00:00
|
|
|
} else if (!_music[0]._fading && _music[1]._fading) {
|
2004-01-14 08:06:56 +00:00
|
|
|
// Stream 1 is fading, so pick that one.
|
2004-01-03 11:24:39 +00:00
|
|
|
primaryStream = 1;
|
2004-01-14 08:06:56 +00:00
|
|
|
} else {
|
|
|
|
// Both streams are fading. Pick the one that is
|
|
|
|
// closest to silent.
|
|
|
|
if (ABS(_music[0]._fading) < ABS(_music[1]._fading))
|
|
|
|
primaryStream = 0;
|
|
|
|
else
|
|
|
|
primaryStream = 1;
|
|
|
|
}
|
2003-09-03 18:59:02 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
_music[primaryStream].stop();
|
|
|
|
}
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
// Pick the available music stream. If no music is playing it doesn't
|
|
|
|
// matter which we use, so pick the first one.
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (_music[0]._streaming || _music[1]._streaming) {
|
|
|
|
if (_music[0]._streaming) {
|
|
|
|
primaryStream = 1;
|
|
|
|
secondaryStream = 0;
|
|
|
|
} else {
|
|
|
|
primaryStream = 0;
|
|
|
|
secondaryStream = 1;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
primaryStream = 0;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
// Don't start streaming if the volume is off.
|
|
|
|
if (isMusicMute())
|
|
|
|
return RD_OK;
|
2003-09-27 17:00:15 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
// Start other music stream fading out
|
|
|
|
if (secondaryStream != -1)
|
|
|
|
_music[secondaryStream].fadeDown();
|
2003-09-16 07:11:29 +00:00
|
|
|
|
2004-08-28 14:50:44 +00:00
|
|
|
switch (openSoundFile(&fpMus, "music")) {
|
|
|
|
case 0:
|
|
|
|
return RDERR_INVALIDFILENAME;
|
|
|
|
case kCLUMode:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
warning("Compressed music is not yet supported");
|
2004-08-27 08:31:33 +00:00
|
|
|
return RDERR_INVALIDFILENAME;
|
2004-01-03 11:24:39 +00:00
|
|
|
}
|
|
|
|
|
2004-08-28 14:50:44 +00:00
|
|
|
return _music[primaryStream].play(musicId, looping);
|
2004-01-03 11:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the time left for the current music, in seconds.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int32 Sound::musicTimeRemaining(void) {
|
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
|
|
|
|
for (int i = 0; i < MAXMUS; i++) {
|
|
|
|
if (_music[i]._streaming && _music[i]._fading <= 0)
|
|
|
|
return (_music[i]._fileEnd - _music[i]._filePos) / _music[i].getRate();
|
|
|
|
}
|
|
|
|
|
|
|
|
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 (getSpeechStatus() == RDSE_SAMPLEPLAYING) {
|
|
|
|
byte volume = mute ? 0 : 16 * _speechVol;
|
|
|
|
|
|
|
|
_vm->_mixer->setChannelVolume(_soundHandleSpeech, volume);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the speech's mute state, true if mute, false if not mute
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool Sound::isSpeechMute(void) {
|
|
|
|
return _speechMuted;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the volume of any future as well as playing speech samples.
|
|
|
|
* @param volume volume, from 0 (silent) to 14 (max)
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Sound::setSpeechVolume(uint8 volume) {
|
|
|
|
if (volume > 14)
|
|
|
|
volume = 14;
|
|
|
|
|
|
|
|
_speechVol = volume;
|
|
|
|
|
|
|
|
if (_soundHandleSpeech.isActive() && !_speechMuted && getSpeechStatus() == RDSE_SAMPLEPLAYING) {
|
|
|
|
_vm->_mixer->setChannelVolume(_soundHandleSpeech, 16 * _speechVol);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the volume setting for speech
|
|
|
|
*/
|
|
|
|
|
|
|
|
uint8 Sound::getSpeechVolume(void) {
|
|
|
|
return _speechVol;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stops the speech dead in its tracks.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Sound::pauseSpeech(void) {
|
|
|
|
if (getSpeechStatus() == RDSE_SAMPLEPLAYING) {
|
|
|
|
_speechPaused = true;
|
|
|
|
_vm->_mixer->pauseHandle(_soundHandleSpeech, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restarts the speech from where it was stopped.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Sound::unpauseSpeech(void) {
|
|
|
|
if (_speechPaused) {
|
|
|
|
_speechPaused = false;
|
|
|
|
_vm->_mixer->pauseHandle(_soundHandleSpeech, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stops the speech from playing.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int32 Sound::stopSpeech(void) {
|
|
|
|
if (!_soundOn)
|
|
|
|
return RD_OK;
|
|
|
|
|
|
|
|
if (_speechStatus) {
|
|
|
|
_vm->_mixer->stopHandle(_soundHandleSpeech);
|
|
|
|
_speechStatus = false;
|
|
|
|
return RD_OK;
|
|
|
|
}
|
|
|
|
return RDERR_SPEECHNOTPLAYING;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Either RDSE_SAMPLEPLAYING or RDSE_SAMPLEFINISHED
|
|
|
|
*/
|
|
|
|
|
|
|
|
int32 Sound::getSpeechStatus(void) {
|
|
|
|
if (!_soundOn || !_speechStatus)
|
|
|
|
return RDSE_SAMPLEFINISHED;
|
|
|
|
|
|
|
|
if (_speechPaused)
|
|
|
|
return RDSE_SAMPLEPLAYING;
|
|
|
|
|
|
|
|
if (!_soundHandleSpeech.isActive()) {
|
|
|
|
_speechStatus = false;
|
|
|
|
return RDSE_SAMPLEFINISHED;
|
|
|
|
}
|
|
|
|
return RDSE_SAMPLEPLAYING;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns either RDSE_QUIET or RDSE_SPEAKING
|
|
|
|
*/
|
|
|
|
|
|
|
|
int32 Sound::amISpeaking(void) {
|
|
|
|
if (!_speechMuted && !_speechPaused && _soundHandleSpeech.isActive())
|
|
|
|
return RDSE_SPEAKING;
|
|
|
|
|
|
|
|
return RDSE_QUIET;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function loads and decompresses a list of speech from a cluster, but
|
2004-08-25 06:55:15 +00:00
|
|
|
* does not play it. This is used for cutscene voice-overs, presumably to
|
|
|
|
* avoid having to read from more than one file on the CD during playback.
|
2004-01-03 11:24:39 +00:00
|
|
|
* @param speechid the text line id used to reference the speech
|
|
|
|
* @param buf a pointer to the buffer that will be allocated for the sound
|
2004-08-22 14:28:11 +00:00
|
|
|
*/
|
2004-01-03 11:24:39 +00:00
|
|
|
|
2004-08-22 14:28:11 +00:00
|
|
|
uint32 Sound::preFetchCompSpeech(uint32 speechid, uint16 **buf) {
|
2004-01-03 11:24:39 +00:00
|
|
|
File fp;
|
2004-08-25 06:55:15 +00:00
|
|
|
uint32 numSamples;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
AudioStream *input = getAudioStream(&fp, "speech", speechid, &numSamples);
|
2004-08-23 06:17:40 +00:00
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
*buf = NULL;
|
2003-09-16 07:11:29 +00:00
|
|
|
|
2004-08-22 14:28:11 +00:00
|
|
|
// Decompress data into speech buffer.
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
uint32 bufferSize = 2 * numSamples;
|
2004-08-22 14:28:11 +00:00
|
|
|
|
|
|
|
*buf = (uint16 *) malloc(bufferSize);
|
|
|
|
if (!*buf) {
|
|
|
|
delete input;
|
2003-08-30 18:06:08 +00:00
|
|
|
fp.close();
|
2003-09-22 06:36:38 +00:00
|
|
|
return 0;
|
2003-08-30 18:06:08 +00:00
|
|
|
}
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
uint32 readSamples = input->readBuffer((int16 *) *buf, numSamples);
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-08-22 14:28:11 +00:00
|
|
|
fp.close();
|
2004-08-25 06:55:15 +00:00
|
|
|
delete input;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
return 2 * readSamples;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
*/
|
|
|
|
|
2004-08-22 14:28:11 +00:00
|
|
|
int32 Sound::playCompSpeech(uint32 speechid, uint8 vol, int8 pan) {
|
2004-01-03 11:24:39 +00:00
|
|
|
if (_speechMuted)
|
|
|
|
return RD_OK;
|
|
|
|
|
|
|
|
if (getSpeechStatus() == RDERR_SPEECHPLAYING)
|
|
|
|
return RDERR_SPEECHPLAYING;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
File *fp = new File;
|
|
|
|
AudioStream *input = getAudioStream(fp, "speech", speechid, NULL);
|
2003-09-16 07:11:29 +00:00
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
// Make the AudioStream object the sole owner of the file so that it
|
|
|
|
// will die along with the AudioStream when the speech has finished.
|
|
|
|
fp->decRef();
|
|
|
|
|
|
|
|
if (!input)
|
|
|
|
return RDERR_INVALIDID;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
// Modify the volume according to the master volume
|
2003-09-21 14:26:25 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
byte volume = _speechMuted ? 0 : vol * _speechVol;
|
|
|
|
int8 p = _panTable[pan + 16];
|
2003-08-30 18:06:08 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
// Start the speech playing
|
|
|
|
_speechPaused = true;
|
2003-08-31 20:26:21 +00:00
|
|
|
|
2004-08-25 06:55:15 +00:00
|
|
|
_vm->_mixer->playInputStream(&_soundHandleSpeech, input, false, volume, p);
|
2004-01-03 11:24:39 +00:00
|
|
|
_speechStatus = true;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2003-09-16 07:11:29 +00:00
|
|
|
return RD_OK;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// SOUND EFFECTS
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
2004-01-03 11:24:39 +00:00
|
|
|
* @return the index of the sound effect with the ID passed in.
|
2003-09-27 17:00:15 +00:00
|
|
|
*/
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
int32 Sound::getFxIndex(int32 id) {
|
|
|
|
for (int i = 0; i < MAXFX; i++) {
|
|
|
|
if (_fx[i]._id == id)
|
|
|
|
return i;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
2004-01-03 11:24:39 +00:00
|
|
|
|
|
|
|
return MAXFX;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
2004-01-03 11:24:39 +00:00
|
|
|
* 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).
|
2003-09-27 17:00:15 +00:00
|
|
|
*/
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::muteFx(bool mute) {
|
|
|
|
_fxMuted = mute;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
// Now update the volume of any fxs playing
|
|
|
|
for (int i = 0; i < MAXFX; i++) {
|
|
|
|
if (_fx[i]._id) {
|
|
|
|
byte volume = mute ? 0 : _fx[i]._volume * _fxVol;
|
2003-09-27 17:00:15 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
_vm->_mixer->setChannelVolume(_fx[i]._handle, volume);
|
|
|
|
}
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
2004-01-03 11:24:39 +00:00
|
|
|
* @return the sound effects's mute state, true if mute, false if not mute
|
2003-09-27 17:00:15 +00:00
|
|
|
*/
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
bool Sound::isFxMute(void) {
|
|
|
|
return _fxMuted;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
2004-01-03 11:24:39 +00:00
|
|
|
* @return the master volume setting for sound effects
|
2003-09-27 17:00:15 +00:00
|
|
|
*/
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
uint8 Sound::getFxVolume(void) {
|
|
|
|
return _fxVol;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
2004-01-03 11:24:39 +00:00
|
|
|
* Set the master volume of all sound effects. The effects still have their
|
|
|
|
* own volume setting as well as the master volume.
|
|
|
|
* @param volume volume, from 0 (silent) to 14 (max)
|
2003-09-27 17:00:15 +00:00
|
|
|
*/
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::setFxVolume(uint8 volume) {
|
|
|
|
if (volume > 14)
|
|
|
|
volume = 14;
|
2003-08-30 18:06:08 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
_fxVol = volume;
|
2003-09-21 14:26:25 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (_fxMuted)
|
|
|
|
return;
|
2003-10-01 06:36:25 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
// Now update the volume of any fxs playing
|
|
|
|
for (int i = 0; i < MAXFX; i++)
|
|
|
|
if (_fx[i]._id)
|
|
|
|
_vm->_mixer->setChannelVolume(_fx[i]._handle, _fx[i]._volume * _fxVol);
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2003-10-04 01:09:29 +00:00
|
|
|
int32 Sound::setFxIdVolumePan(int32 id, uint8 vol, int8 pan) {
|
2003-10-01 06:36:25 +00:00
|
|
|
int32 i = getFxIndex(id);
|
|
|
|
|
2003-07-28 01:47:41 +00:00
|
|
|
if (i == MAXFX)
|
|
|
|
return RDERR_FXNOTOPEN;
|
|
|
|
|
2003-10-29 07:53:05 +00:00
|
|
|
if (vol > 14)
|
|
|
|
vol = 14;
|
|
|
|
|
2003-10-01 06:36:25 +00:00
|
|
|
_fx[i]._volume = vol;
|
2003-10-29 07:53:05 +00:00
|
|
|
|
2003-10-01 06:36:25 +00:00
|
|
|
if (!_fxMuted) {
|
2003-11-16 14:18:29 +00:00
|
|
|
_vm->_mixer->setChannelVolume(_fx[i]._handle, _fx[i]._volume * _fxVol);
|
2004-01-29 18:15:27 +00:00
|
|
|
_vm->_mixer->setChannelBalance(_fx[i]._handle, _panTable[pan + 16]);
|
2003-08-30 18:06:08 +00:00
|
|
|
}
|
2003-10-29 07:53:05 +00:00
|
|
|
|
2003-07-28 01:47:41 +00:00
|
|
|
return RD_OK;
|
|
|
|
}
|
|
|
|
|
2003-10-04 01:09:29 +00:00
|
|
|
int32 Sound::setFxIdVolume(int32 id, uint8 vol) {
|
2003-10-01 06:36:25 +00:00
|
|
|
int32 i = getFxIndex(id);
|
2003-09-21 14:26:25 +00:00
|
|
|
|
2003-07-28 01:47:41 +00:00
|
|
|
if (i == MAXFX)
|
|
|
|
return RDERR_FXNOTOPEN;
|
|
|
|
|
2003-10-01 06:36:25 +00:00
|
|
|
_fx[i]._volume = vol;
|
2004-01-03 11:24:39 +00:00
|
|
|
|
2003-10-01 06:36:25 +00:00
|
|
|
if (!_fxMuted)
|
2003-11-16 14:18:29 +00:00
|
|
|
_vm->_mixer->setChannelVolume(_fx[i]._handle, vol * _fxVol);
|
2003-09-21 14:26:25 +00:00
|
|
|
|
2003-07-28 01:47:41 +00:00
|
|
|
return RD_OK;
|
|
|
|
}
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::pauseFx(void) {
|
|
|
|
if (_fxPaused)
|
2003-10-01 06:36:25 +00:00
|
|
|
return;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2003-09-21 14:26:25 +00:00
|
|
|
for (int i = 0; i < MAXFX; i++) {
|
2004-01-03 11:24:39 +00:00
|
|
|
if (_fx[i]._id) {
|
|
|
|
_vm->_mixer->pauseHandle(_fx[i]._handle, true);
|
|
|
|
_fx[i]._paused = true;
|
|
|
|
} else
|
2003-10-01 06:36:25 +00:00
|
|
|
_fx[i]._paused = false;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
_fxPaused = true;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2003-10-04 01:09:29 +00:00
|
|
|
void Sound::pauseFxForSequence(void) {
|
2004-01-03 11:24:39 +00:00
|
|
|
if (_fxPaused)
|
|
|
|
return;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
for (int i = 0; i < MAXFX; i++) {
|
|
|
|
if (_fx[i]._id && _fx[i]._id != -2) {
|
|
|
|
_vm->_mixer->pauseHandle(_fx[i]._handle, true);
|
|
|
|
_fx[i]._paused = true;
|
|
|
|
} else
|
|
|
|
_fx[i]._paused = false;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
2003-09-27 17:00:15 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
_fxPaused = true;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::unpauseFx(void) {
|
|
|
|
if (!_fxPaused)
|
|
|
|
return;
|
2003-10-29 07:53:05 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
for (int i = 0; i < MAXFX; i++)
|
|
|
|
if (_fx[i]._paused && _fx[i]._id)
|
|
|
|
_vm->_mixer->pauseHandle(_fx[i]._handle, false);
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
_fxPaused = false;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
bool Sound::isFxPlaying(int32 id) {
|
|
|
|
int i;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
i = getFxIndex(id);
|
|
|
|
if (i == MAXFX)
|
|
|
|
return false;
|
2003-09-27 17:00:15 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
return _fx[i]._handle.isActive();
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
2004-01-03 11:24:39 +00:00
|
|
|
* This function closes a sound effect which has been previously opened for
|
|
|
|
* playing. Sound effects must be closed when they are finished with, otherwise
|
|
|
|
* you will run out of sound effect buffers.
|
|
|
|
* @param id the id of the sound to close
|
2003-09-27 17:00:15 +00:00
|
|
|
*/
|
|
|
|
|
2004-05-01 10:42:23 +00:00
|
|
|
int32 Sound::stopFx(int32 id) {
|
2004-01-03 11:24:39 +00:00
|
|
|
int i;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (!_soundOn)
|
|
|
|
return RD_OK;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
i = getFxIndex(id);
|
2003-09-27 17:00:15 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (i == MAXFX)
|
|
|
|
return RDERR_FXNOTOPEN;
|
2003-09-04 10:58:55 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
stopFxHandle(i);
|
|
|
|
return RD_OK;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
2004-01-03 11:24:39 +00:00
|
|
|
* This function plays a sound effect. If the effect has already been opened
|
|
|
|
* then 'data' should be NULL, and the sound effect will simply be obtained
|
|
|
|
* from the id passed in. If the effect has not been opened, then the WAV data
|
|
|
|
* should be passed in 'data'. The sound effect will be closed when it has
|
|
|
|
* finished playing.
|
|
|
|
* @param id the sound id
|
|
|
|
* @param data either NULL or the WAV data
|
|
|
|
* @param vol volume, 0 (minimum) to 16 (maximum)
|
|
|
|
* @param pan panning, -16 (full left) to 16 (full right)
|
|
|
|
* @param type either RDSE_FXSPOT or RDSE_FXLOOP
|
|
|
|
* @warning Zero is not a valid id
|
2003-09-27 17:00:15 +00:00
|
|
|
*/
|
|
|
|
|
2004-04-23 07:02:11 +00:00
|
|
|
int32 Sound::playFx(int32 id, byte *data, uint8 vol, int8 pan, uint8 type) {
|
2004-01-03 11:24:39 +00:00
|
|
|
if (!_soundOn)
|
|
|
|
return RD_OK;
|
2003-09-04 10:58:55 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
byte volume = _fxMuted ? 0 : vol * _fxVol;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-05-01 10:42:23 +00:00
|
|
|
// All lead-ins and lead-outs I've heard are music, so we use
|
|
|
|
// the music volume setting for them.
|
2003-09-27 17:00:15 +00:00
|
|
|
|
2004-05-01 10:42:23 +00:00
|
|
|
if (type == RDSE_FXLEADIN || type == RDSE_FXLEADOUT) {
|
|
|
|
id = (type == RDSE_FXLEADIN) ? -2 : -1;
|
|
|
|
volume = _musicMuted ? 0 : _musicVolTable[_musicVol];
|
|
|
|
}
|
|
|
|
|
|
|
|
WavInfo wavInfo;
|
2003-09-04 10:58:55 +00:00
|
|
|
|
2004-05-01 10:42:23 +00:00
|
|
|
if (!getWavInfo(data, &wavInfo)) {
|
|
|
|
warning("playFx: Not a valid WAV file");
|
|
|
|
return RDERR_INVALIDWAV;
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2004-05-01 10:42:23 +00:00
|
|
|
int32 fxi = getFxIndex(id);
|
2003-09-27 17:00:15 +00:00
|
|
|
|
2004-05-01 10:42:23 +00:00
|
|
|
if (fxi == MAXFX) {
|
|
|
|
// Find a free slot
|
|
|
|
fxi = getFxIndex(0);
|
|
|
|
|
|
|
|
if (fxi == MAXFX) {
|
|
|
|
warning("openFx: Running out of sound slots");
|
|
|
|
|
|
|
|
// There aren't any free sound handles available. This
|
|
|
|
// usually shouldn't happen, but if it does we expire
|
|
|
|
// the first sound effect that isn't currently playing.
|
|
|
|
|
|
|
|
for (fxi = 0; fxi < MAXFX; fxi++)
|
|
|
|
if (!_fx[fxi]._handle.isActive())
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Still no dice? I give up!
|
|
|
|
|
|
|
|
if (fxi == MAXFX) {
|
|
|
|
warning("openFx: No free sound slots");
|
|
|
|
return RDERR_NOFREEBUFFERS;
|
|
|
|
}
|
2004-01-03 11:24:39 +00:00
|
|
|
}
|
2004-05-01 10:42:23 +00:00
|
|
|
|
|
|
|
_fx[fxi]._id = id;
|
2004-01-03 11:24:39 +00:00
|
|
|
}
|
2003-10-29 07:53:05 +00:00
|
|
|
|
2004-05-01 10:42:23 +00:00
|
|
|
if (_fx[fxi]._handle.isActive())
|
|
|
|
return RDERR_FXALREADYOPEN;
|
|
|
|
|
|
|
|
uint32 flags = SoundMixer::FLAG_16BITS | SoundMixer::FLAG_LITTLE_ENDIAN;
|
|
|
|
|
|
|
|
if (wavInfo.channels == 2)
|
|
|
|
flags |= SoundMixer::FLAG_STEREO;
|
|
|
|
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
if (type == RDSE_FXLOOP)
|
2004-05-01 10:42:23 +00:00
|
|
|
flags |= SoundMixer::FLAG_LOOP;
|
2004-01-03 11:24:39 +00:00
|
|
|
else
|
2004-05-01 10:42:23 +00:00
|
|
|
flags &= ~SoundMixer::FLAG_LOOP;
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-05-01 10:42:23 +00:00
|
|
|
_fx[fxi]._volume = vol;
|
|
|
|
|
|
|
|
int8 p = _panTable[pan + 16];
|
2003-09-27 17:00:15 +00:00
|
|
|
|
2004-05-01 10:42:23 +00:00
|
|
|
_vm->_mixer->playRaw(&_fx[fxi]._handle, wavInfo.data, wavInfo.samples, wavInfo.rate, flags, -1, volume, p);
|
2003-07-28 01:47:41 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
return RD_OK;
|
|
|
|
}
|
2003-09-27 17:00:15 +00:00
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::stopFxHandle(int i) {
|
|
|
|
if (_fx[i]._id) {
|
|
|
|
_vm->_mixer->stopHandle(_fx[i]._handle);
|
|
|
|
_fx[i]._id = 0;
|
|
|
|
_fx[i]._paused = false;
|
|
|
|
}
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2003-09-27 17:00:15 +00:00
|
|
|
/**
|
2004-01-03 11:24:39 +00:00
|
|
|
* This function clears all of the sound effects which are currently open or
|
|
|
|
* playing, irrespective of type.
|
2003-09-27 17:00:15 +00:00
|
|
|
*/
|
|
|
|
|
2004-01-03 11:24:39 +00:00
|
|
|
void Sound::clearAllFx(void) {
|
|
|
|
if (!_soundOn)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (int i = 0; i < MAXFX; i++)
|
|
|
|
if (_fx[i]._id && _fx[i]._id != -1 && _fx[i]._id != -2)
|
|
|
|
stopFxHandle(i);
|
2003-07-28 01:47:41 +00:00
|
|
|
}
|
2003-10-04 00:52:27 +00:00
|
|
|
|
|
|
|
} // End of namespace Sword2
|