mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-25 12:05:53 +00:00
51ffd1d249
This fixes #15094.
1433 lines
42 KiB
C++
1433 lines
42 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
#include "common/config-manager.h"
|
|
#include "common/endian.h"
|
|
#include "common/memstream.h"
|
|
#include "common/textconsole.h"
|
|
#include "common/util.h"
|
|
|
|
#include "sword1/sound.h"
|
|
#include "sword1/resman.h"
|
|
#include "sword1/logic.h"
|
|
#include "sword1/sword1.h"
|
|
|
|
#include "audio/audiostream.h"
|
|
#include "audio/decoders/flac.h"
|
|
#include "audio/decoders/mp3.h"
|
|
#include "audio/decoders/raw.h"
|
|
#include "audio/decoders/vorbis.h"
|
|
#include "audio/decoders/xa.h"
|
|
#include "audio/decoders/aiff.h"
|
|
|
|
namespace Sword1 {
|
|
|
|
#define SPEECH_FLAGS (Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN)
|
|
|
|
Sound::Sound(Audio::Mixer *mixer, SwordEngine *vm, ResMan *pResMan)
|
|
: _rnd("sword1sound") {
|
|
_vm = vm;
|
|
_mixer = mixer;
|
|
_resMan = pResMan;
|
|
_bigEndianSpeech = false;
|
|
_cowHeader = nullptr;
|
|
_cowMode = CowWave;
|
|
_endOfQueue = 0;
|
|
_currentCowFile = 0;
|
|
|
|
_musicOutputStream[0] = Audio::makeQueuingAudioStream(DEFAULT_SAMPLE_RATE, false);
|
|
_musicOutputStream[1] = Audio::makeQueuingAudioStream(DEFAULT_SAMPLE_RATE, false);
|
|
getVolumes();
|
|
|
|
for (uint i = 0; i < ARRAYSIZE(_fxQueue); i++) _fxQueue[i].reset();
|
|
}
|
|
|
|
Sound::~Sound() {
|
|
// clean up fx queue
|
|
_mixer->stopAll();
|
|
for (uint8 cnt = 0; cnt < _endOfQueue; cnt++)
|
|
if (_fxQueue[cnt].delay == 0)
|
|
_resMan->resClose(getSampleId(_fxQueue[cnt].id));
|
|
_endOfQueue = 0;
|
|
closeCowSystem();
|
|
}
|
|
|
|
uint32 Sound::getSampleId(int32 fxNo) {
|
|
byte cluster = _fxList[fxNo].sampleId.cluster;
|
|
byte id;
|
|
if (SwordEngine::_systemVars.isDemo && SwordEngine::_systemVars.platform == Common::kPlatformWindows && !SwordEngine::_systemVars.isSpanishDemo) {
|
|
id = _fxList[fxNo].sampleId.idWinDemo;
|
|
} else {
|
|
id = _fxList[fxNo].sampleId.idStd;
|
|
}
|
|
return (cluster << 24) | id;
|
|
}
|
|
|
|
void Sound::checkSpeechFileEndianness() {
|
|
// Some mac versions (not all of them) use big endian wav, although
|
|
// the wav header doesn't indicate it.
|
|
// Use heuristic to determine endianness of speech.
|
|
// The heuristic consist in computing the sum of the absolute difference for
|
|
// every two consecutive samples. This is done both with a big endian and a
|
|
// little endian assumption. The one with the smallest sum should be the
|
|
// correct one (the sound wave is supposed to be relatively smooth).
|
|
// It needs at least 1000 samples to get stable result (the code below is
|
|
// using the first 2000 samples of the wav sound).
|
|
|
|
// Init speech file if not already done.
|
|
if (!_currentCowFile) {
|
|
// Open one of the speech files. It uses SwordEngine::_systemVars.currentCD
|
|
// to decide which file to open, therefore if it is currently set to zero
|
|
// we have to set it to either 1 or 2 (I decided to set it to 1 as this is
|
|
// more likely to be the first file that will be needed).
|
|
bool no_current_cd = false;
|
|
if (SwordEngine::_systemVars.currentCD == 0) {
|
|
SwordEngine::_systemVars.currentCD = 1;
|
|
no_current_cd = true;
|
|
}
|
|
initCowSystem();
|
|
if (no_current_cd) {
|
|
// In case it fails with CD1 retry with CD2
|
|
if (!_currentCowFile) {
|
|
SwordEngine::_systemVars.currentCD = 2;
|
|
initCowSystem();
|
|
}
|
|
// Reset currentCD flag
|
|
SwordEngine::_systemVars.currentCD = 0;
|
|
}
|
|
}
|
|
|
|
// Testing for endianness makes sense only if using the uncompressed files.
|
|
if (_cowHeader == nullptr || (_cowMode != CowWave && _cowMode != CowDemo))
|
|
return;
|
|
|
|
// I picked the sample to use randomly (I just made sure it is long enough so that there is
|
|
// a fair chance of the heuristic to have a stable result and work for every language).
|
|
int roomNo = _currentCowFile == 1 ? 1 : 129;
|
|
int localNo = _currentCowFile == 1 ? 2 : 933;
|
|
// Get the speech data and apply the heuristic
|
|
uint32 locIndex = _cowHeader[roomNo] >> 2;
|
|
uint32 sampleSize = _cowHeader[locIndex + (localNo * 2)];
|
|
uint32 index = _cowHeader[locIndex + (localNo * 2) - 1];
|
|
if (sampleSize) {
|
|
uint32 size = 0;
|
|
bool leOk = false, beOk = false;
|
|
// Compute average of difference between two consecutive samples for both BE and LE
|
|
|
|
_bigEndianSpeech = false;
|
|
byte *compSample = (byte *)malloc(sampleSize);
|
|
_cowFile.seek(index + _cowHeaderSize, SEEK_SET);
|
|
_cowFile.read(compSample, sampleSize);
|
|
byte *data = (byte *)malloc(getSpeechSize(compSample, sampleSize));
|
|
expandSpeech(compSample, data, sampleSize, &leOk, &size);
|
|
uint32 maxSamples = size > 2000 ? 2000 : size;
|
|
double le_diff = endiannessHeuristicValue((int16 *)data, size, maxSamples);
|
|
free(data);
|
|
data = nullptr;
|
|
|
|
_bigEndianSpeech = true;
|
|
_cowFile.seek(index + _cowHeaderSize, SEEK_SET);
|
|
_cowFile.read(compSample, sampleSize);
|
|
data = (byte *)malloc(getSpeechSize(compSample, sampleSize));
|
|
expandSpeech(compSample, data, sampleSize, &beOk, &size);
|
|
double be_diff = endiannessHeuristicValue((int16 *)data, size, maxSamples);
|
|
free(data);
|
|
|
|
free(compSample);
|
|
|
|
// Set the big endian flag
|
|
if (leOk && !beOk)
|
|
_bigEndianSpeech = false;
|
|
else if (beOk && !leOk)
|
|
_bigEndianSpeech = true;
|
|
else
|
|
_bigEndianSpeech = (be_diff < le_diff);
|
|
|
|
if (_bigEndianSpeech)
|
|
debug(6, "Mac version: using big endian speech file");
|
|
else
|
|
debug(6, "Mac version: using little endian speech file");
|
|
|
|
debug(8, "Speech decompression memory check: big endian = %s, little endian = %s", beOk ? "good" : "bad", leOk ? "good" : "bad");
|
|
debug(8, "Speech endianness heuristic: average = %f for BE and %f for LE (%d samples)", be_diff, le_diff, maxSamples);
|
|
}
|
|
}
|
|
|
|
double Sound::endiannessHeuristicValue(int16* data, uint32 dataSize, uint32 &maxSamples) {
|
|
if (!data)
|
|
return 50000.; // the heuristic value for the wrong endianess is about 21000 (1/3rd of the 16 bits range)
|
|
|
|
double diff_sum = 0.;
|
|
uint32 cpt = 0;
|
|
int16 prev_value = (int16)FROM_LE_16(*((uint16 *)(data)));
|
|
for (uint32 i = 1; i < dataSize && cpt < maxSamples; ++i) {
|
|
int16 value = (int16)FROM_LE_16(*((uint16 *)(data + i)));
|
|
if (value != prev_value) {
|
|
diff_sum += fabs((double)(value - prev_value));
|
|
++cpt;
|
|
prev_value = value;
|
|
}
|
|
}
|
|
if (cpt == 0)
|
|
return 50000.;
|
|
maxSamples = cpt;
|
|
return diff_sum / cpt;
|
|
}
|
|
|
|
int Sound::addToQueue(uint32 fxNo) {
|
|
bool alreadyInQueue = false;
|
|
for (uint8 cnt = 0; cnt < _endOfQueue; cnt++) {
|
|
if (_fxQueue[cnt].id == fxNo) {
|
|
alreadyInQueue = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (alreadyInQueue) {
|
|
debug(5, "Sound::addToQueue(): Sound %d is already in the queue, ignoring...", fxNo);
|
|
return 0;
|
|
} else {
|
|
if (_endOfQueue == MAX_FXQ_LENGTH) {
|
|
warning("Sound::addToQueue(): Sound queue overflow");
|
|
return 0;
|
|
}
|
|
|
|
uint32 sampleId = getSampleId(fxNo);
|
|
if ((sampleId & 0xFF) != 0xFF) {
|
|
_resMan->resOpen(sampleId);
|
|
_fxQueue[_endOfQueue].id = fxNo;
|
|
|
|
if (_fxList[fxNo].type == FX_SPOT) {
|
|
_fxQueue[_endOfQueue].delay = _fxList[fxNo].delay + 1;
|
|
} else {
|
|
_fxQueue[_endOfQueue].delay = 1;
|
|
}
|
|
|
|
_endOfQueue++;
|
|
return 1; // Success!
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void Sound::removeFromQueue(uint32 fxNo) {
|
|
bool alreadyInQueue = false;
|
|
int cnt = 0;
|
|
|
|
for (cnt = 0; cnt < _endOfQueue; cnt++) {
|
|
if (_fxQueue[cnt].id == fxNo) {
|
|
alreadyInQueue = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (alreadyInQueue) {
|
|
_resMan->resClose(getSampleId(_fxQueue[cnt].id));
|
|
|
|
for (int j = 0; j < _endOfQueue; j++) {
|
|
if (_fxQueue[j].id == fxNo) {
|
|
// Move all the others down one to fill this space...
|
|
for (int i = j; i < (_endOfQueue - 1); i++) {
|
|
_fxQueue[i].id = _fxQueue[i + 1].id;
|
|
_fxQueue[i].delay = _fxQueue[i + 1].delay;
|
|
}
|
|
|
|
debug(5, "Sound::addToQueue(): Sound fxNo %d removed from _fxQueue[%d] (_endOfQueue = %d)", fxNo, j, _endOfQueue - 1);
|
|
_endOfQueue--;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sound::engine() {
|
|
// Update music streaming...
|
|
updateMusicStreaming();
|
|
|
|
// Add any random sfx to the queue...
|
|
for (uint16 cnt = 0; cnt < TOTAL_FX_PER_ROOM; cnt++) {
|
|
uint16 fxNo = _roomsFixedFx[Logic::_scriptVars[SCREEN]][cnt];
|
|
if (fxNo) {
|
|
if (_fxList[fxNo].type == FX_RANDOM) {
|
|
if (_rnd.getRandomNumber(_fxList[fxNo].delay) == 0)
|
|
addToQueue(fxNo);
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
|
|
// Now process the queue...
|
|
int32 fxNo = 0;
|
|
for (uint8 cnt = 0; cnt < _endOfQueue; cnt++) {
|
|
fxNo = _fxQueue[cnt].id;
|
|
if (_fxQueue[cnt].delay > 0) {
|
|
_fxQueue[cnt].delay--;
|
|
if (_fxQueue[cnt].delay == 0)
|
|
playSample(fxNo);
|
|
} else if (checkSampleStatus(fxNo) == S_STATUS_FINISHED) {
|
|
// Delay countdown was already zero, so the sample has
|
|
// already been played, so check if it's finished...
|
|
removeFromQueue(fxNo);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Sound::amISpeaking() {
|
|
int16 *offset;
|
|
int16 count;
|
|
int32 readPos;
|
|
|
|
if (!_speechSampleBusy)
|
|
return false;
|
|
|
|
if (_mixer->isSoundHandleActive(_hSampleSpeech)) {
|
|
_speechLipsyncCounter += 1;
|
|
|
|
readPos = _speechLipsyncCounter * 919 * 2;
|
|
|
|
// Ensure that we don't read beyond the buffer...
|
|
if (readPos + (int32)(150 * sizeof(int16)) > _speechSize)
|
|
return false;
|
|
|
|
offset = (int16 *)&_speechSample[readPos];
|
|
count = 0;
|
|
for (int i = 0; i < 150; i++) {
|
|
if ((offset[i] < NEG_MOUTH_THRESHOLD) || (offset[i] > POS_MOUTH_THRESHOLD)) {
|
|
count += 1;
|
|
if (count == 50) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Sound::newScreen(uint32 screen) {
|
|
if (_currentCowFile != SwordEngine::_systemVars.currentCD) {
|
|
if (_cowFile.isOpen())
|
|
closeCowSystem();
|
|
initCowSystem();
|
|
}
|
|
|
|
// Start the room's looping sounds.
|
|
for (uint16 cnt = 0; cnt < TOTAL_FX_PER_ROOM; cnt++) {
|
|
uint16 fxNo = _roomsFixedFx[screen][cnt];
|
|
if (fxNo) {
|
|
if (_fxList[fxNo].type == FX_LOOP)
|
|
addToQueue(fxNo);
|
|
} else
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Sound::startSpeech(uint16 roomNo, uint16 localNo) {
|
|
if (_cowHeader == nullptr) {
|
|
warning("Sound::startSpeech: COW file isn't open");
|
|
return;
|
|
}
|
|
|
|
uint32 locIndex = 0xFFFFFFFF;
|
|
uint32 sampleFileSize = 0;
|
|
uint32 index = 0;
|
|
|
|
if (_cowMode == CowPSX) {
|
|
Common::File file;
|
|
uint16 i;
|
|
|
|
if (!file.open("speech.lis")) {
|
|
warning("Could not open speech.lis");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; !file.eos() && !file.err(); i++)
|
|
if (file.readUint16LE() == roomNo) {
|
|
locIndex = i;
|
|
break;
|
|
}
|
|
file.close();
|
|
|
|
if (locIndex == 0xFFFFFFFF) {
|
|
warning("Could not find room %d in speech.lis", roomNo);
|
|
return;
|
|
}
|
|
|
|
if (!file.open("speech.inf")) {
|
|
warning("Could not open speech.inf");
|
|
return;
|
|
}
|
|
|
|
uint16 numRooms = file.readUint16LE();
|
|
|
|
file.seek(locIndex * 4 + 2); // 4 bytes per room, skip first 2 bytes
|
|
|
|
uint16 numLines = file.readUint16LE();
|
|
uint16 roomOffset = file.readUint16LE();
|
|
|
|
// The offset is in terms of uint16's, so multiply by 2. Skip the room indexes too...
|
|
file.seek(2 + numRooms * 4 + roomOffset * 2);
|
|
|
|
locIndex = 0xFFFFFFFF;
|
|
|
|
for (i = 0; i < numLines; i++)
|
|
if (file.readUint16LE() == localNo) {
|
|
locIndex = i;
|
|
break;
|
|
}
|
|
|
|
if (locIndex == 0xFFFFFFFF) {
|
|
warning("Could not find local number %d in room %d in speech.inf", roomNo, localNo);
|
|
return;
|
|
}
|
|
|
|
file.close();
|
|
|
|
index = _cowHeader[(roomOffset + locIndex) * 2];
|
|
sampleFileSize = _cowHeader[(roomOffset + locIndex) * 2 + 1];
|
|
} else {
|
|
locIndex = _cowHeader[roomNo] >> 2;
|
|
sampleFileSize = _cowHeader[locIndex + (localNo * 2)];
|
|
index = _cowHeader[locIndex + (localNo * 2) - 1];
|
|
}
|
|
|
|
debug(6, "Sound::startSpeech(%d, %d): locIndex %d, sampleFileSize %d, index %d", roomNo, localNo, locIndex, sampleFileSize, index);
|
|
|
|
if (sampleFileSize) {
|
|
byte *compSample = (byte *)malloc(sampleFileSize);
|
|
if (compSample) {
|
|
if ((_cowMode == CowWave) || (_cowMode == CowDemo)) {
|
|
_cowFile.seek(index + _cowHeaderSize, SEEK_SET);
|
|
} else if (_cowMode == CowPSX && sampleFileSize != 0xffffffff) {
|
|
_cowFile.seek(index * 2048);
|
|
} else if (_cowMode == CowFLAC || _cowMode == CowVorbis || _cowMode == CowMP3) {
|
|
_cowFile.seek(index);
|
|
}
|
|
|
|
_cowFile.read(compSample, sampleFileSize);
|
|
_speechSize = getSpeechSize(compSample, sampleFileSize);
|
|
|
|
// Force alignment on sample size (bug #15094)...
|
|
if ((_speechSize % 2) != 0)
|
|
_speechSize += 1;
|
|
|
|
_speechSample = (byte *)malloc(_speechSize);
|
|
|
|
if (_speechSample) {
|
|
// Sometimes, the decompressed speech size is slightly longer than the actual
|
|
// decompressed sample data we are about to receive; this is normal, but to
|
|
// fully emulate what the original does, let's zero out the sample buffer.
|
|
memset(_speechSample, 0, _speechSize);
|
|
|
|
SwordEngine::_systemVars.speechRunning = expandSpeech(compSample, _speechSample, sampleFileSize);
|
|
free(compSample);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Sound::expandSpeech(byte *src, byte *dst, uint32 dstSize, bool *endiannessCheck, uint32 *sizeForEndiannessCheck) {
|
|
|
|
if (_cowMode == CowPSX) {
|
|
Audio::RewindableAudioStream *stream = Audio::makeXAStream(new Common::MemoryReadStream(src, dstSize), 11025);
|
|
stream->readBuffer((int16 *)_speechSample, _speechSize);
|
|
return true;
|
|
} else if (_cowMode != CowWave && _cowMode != CowDemo) {
|
|
Audio::RewindableAudioStream *stream = nullptr;
|
|
#ifdef USE_FLAC
|
|
if (_cowMode == CowFLAC) {
|
|
stream = Audio::makeFLACStream(new Common::MemoryReadStream(src, dstSize), DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
#ifdef USE_VORBIS
|
|
if (_cowMode == CowVorbis) {
|
|
stream = Audio::makeVorbisStream(new Common::MemoryReadStream(src, dstSize), DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
#ifdef USE_MAD
|
|
if (_cowMode == CowMP3) {
|
|
stream = Audio::makeMP3Stream(new Common::MemoryReadStream(src, dstSize), DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
if (stream) {
|
|
stream->readBuffer((int16 *)_speechSample, _speechSize);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
uint8 *fBuf = src;
|
|
uint32 headerPos = 0;
|
|
|
|
while ((READ_BE_UINT32(fBuf + headerPos) != 'data') && (headerPos < 100))
|
|
headerPos++;
|
|
|
|
if (headerPos < 100) {
|
|
if (endiannessCheck)
|
|
*endiannessCheck = true;
|
|
int32 resSize;
|
|
int16 *srcData;
|
|
uint32 srcPos;
|
|
int16 length;
|
|
dstSize /= 2;
|
|
headerPos += 4; // skip 'data' tag
|
|
if (_cowMode != CowDemo) {
|
|
resSize = READ_LE_UINT32(fBuf + headerPos) >> 1;
|
|
headerPos += 4;
|
|
} else {
|
|
// the demo speech files have the uncompressed size
|
|
// embedded in the compressed stream *sigh*
|
|
//
|
|
// But not always, apparently. See bug #4002. Is
|
|
// there any way to figure out the size other than
|
|
// decoding the sound in that case?
|
|
|
|
if (fBuf[headerPos + 1] == 0) {
|
|
if (READ_LE_UINT16(fBuf + headerPos) == 1) {
|
|
resSize = READ_LE_UINT16(fBuf + headerPos + 2);
|
|
resSize |= READ_LE_UINT16(fBuf + headerPos + 6) << 16;
|
|
} else
|
|
resSize = READ_LE_UINT32(fBuf + headerPos + 2);
|
|
resSize >>= 1;
|
|
} else {
|
|
resSize = 0;
|
|
srcData = (int16 *)fBuf;
|
|
srcPos = headerPos >> 1;
|
|
while (srcPos < dstSize) {
|
|
length = (int16)READ_LE_UINT16(srcData + srcPos);
|
|
srcPos++;
|
|
if (length < 0) {
|
|
resSize -= length;
|
|
srcPos++;
|
|
} else {
|
|
resSize += length;
|
|
srcPos += length;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert(!(headerPos & 1));
|
|
srcData = (int16 *)fBuf;
|
|
srcPos = headerPos >> 1;
|
|
uint32 dstPos = 0;
|
|
int16 *dstData = (int16 *)dst;
|
|
|
|
int32 samplesLeft = resSize;
|
|
while (srcPos < dstSize && samplesLeft > 0) {
|
|
length = (int16)(_bigEndianSpeech ? READ_BE_UINT16(srcData + srcPos) : READ_LE_UINT16(srcData + srcPos));
|
|
srcPos++;
|
|
if (length < 0) {
|
|
length = -length;
|
|
if (length > samplesLeft) {
|
|
length = samplesLeft;
|
|
if (endiannessCheck)
|
|
*endiannessCheck = false;
|
|
}
|
|
int16 value;
|
|
if (_bigEndianSpeech) {
|
|
value = (int16)SWAP_BYTES_16(*((uint16 *)(srcData + srcPos)));
|
|
} else {
|
|
value = srcData[srcPos];
|
|
}
|
|
for (uint16 cnt = 0; cnt < (uint16)length; cnt++)
|
|
dstData[dstPos++] = value;
|
|
srcPos++;
|
|
} else {
|
|
if (length > samplesLeft) {
|
|
length = samplesLeft;
|
|
if (endiannessCheck)
|
|
*endiannessCheck = false;
|
|
}
|
|
if (_bigEndianSpeech) {
|
|
for (uint16 cnt = 0; cnt < (uint16)length; cnt++)
|
|
dstData[dstPos++] = (int16)SWAP_BYTES_16(*((uint16 *)(srcData + (srcPos++))));
|
|
} else {
|
|
memcpy(dstData + dstPos, srcData + srcPos, length * 2);
|
|
dstPos += length;
|
|
srcPos += length;
|
|
}
|
|
}
|
|
samplesLeft -= length;
|
|
}
|
|
if (samplesLeft > 0) {
|
|
memset(dstData + dstPos, 0, samplesLeft * 2);
|
|
if (endiannessCheck)
|
|
*endiannessCheck = false;
|
|
}
|
|
if (_cowMode == CowDemo) // demo has wave output size embedded in the compressed data
|
|
*(uint32 *)dstData = 0;
|
|
return true;
|
|
} else {
|
|
warning("Sound::expandSpeech(): DATA tag not found in wave header");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Sound::initCowSystem() {
|
|
if (SwordEngine::_systemVars.currentCD == 0)
|
|
return;
|
|
|
|
char cowName[25];
|
|
/* look for speech1/2.clu in the data dir
|
|
and speech/speech.clu (running from cd or using cd layout)
|
|
*/
|
|
#ifdef USE_FLAC
|
|
if (!_cowFile.isOpen()) {
|
|
Common::sprintf_s(cowName, "SPEECH%d.CLF", SwordEngine::_systemVars.currentCD);
|
|
_cowFile.open(cowName);
|
|
if (_cowFile.isOpen()) {
|
|
debug(1, "Using FLAC compressed Speech Cluster");
|
|
_cowMode = CowFLAC;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_VORBIS
|
|
if (!_cowFile.isOpen()) {
|
|
Common::sprintf_s(cowName, "SPEECH%d.CLV", SwordEngine::_systemVars.currentCD);
|
|
_cowFile.open(cowName);
|
|
if (_cowFile.isOpen()) {
|
|
debug(1, "Using Vorbis compressed Speech Cluster");
|
|
_cowMode = CowVorbis;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_MAD
|
|
if (!_cowFile.isOpen()) {
|
|
Common::sprintf_s(cowName, "SPEECH%d.CL3", SwordEngine::_systemVars.currentCD);
|
|
_cowFile.open(cowName);
|
|
if (_cowFile.isOpen()) {
|
|
debug(1, "Using MP3 compressed Speech Cluster");
|
|
_cowMode = CowMP3;
|
|
}
|
|
}
|
|
#endif
|
|
if (!_cowFile.isOpen()) {
|
|
Common::sprintf_s(cowName, "SPEECH%d.CLU", SwordEngine::_systemVars.currentCD);
|
|
_cowFile.open(cowName);
|
|
if (!_cowFile.isOpen()) {
|
|
_cowFile.open("speech.clu");
|
|
}
|
|
debug(1, "Using uncompressed Speech Cluster");
|
|
_cowMode = CowWave;
|
|
}
|
|
|
|
if (SwordEngine::isPsx()) {
|
|
// There's only one file on the PSX, so set it to the current disc.
|
|
_currentCowFile = SwordEngine::_systemVars.currentCD;
|
|
if (!_cowFile.isOpen()) {
|
|
if (!_cowFile.open("speech.dat"))
|
|
error("Could not open speech.dat");
|
|
_cowMode = CowPSX;
|
|
}
|
|
}
|
|
|
|
if (!_cowFile.isOpen())
|
|
_cowFile.open("speech.clu");
|
|
|
|
if (!_cowFile.isOpen()) {
|
|
_cowFile.open("cows.mad");
|
|
if (_cowFile.isOpen())
|
|
_cowMode = CowDemo;
|
|
}
|
|
|
|
if (_cowFile.isOpen()) {
|
|
if (SwordEngine::isPsx()) {
|
|
// Get data from the external table file
|
|
Common::File tableFile;
|
|
if (!tableFile.open("speech.tab"))
|
|
error("Could not open speech.tab");
|
|
_cowHeaderSize = tableFile.size();
|
|
_cowHeader = (uint32 *)malloc(_cowHeaderSize);
|
|
if (_cowHeaderSize & 3)
|
|
error("Unexpected cow header size %d", _cowHeaderSize);
|
|
for (uint32 cnt = 0; cnt < _cowHeaderSize / 4; cnt++)
|
|
_cowHeader[cnt] = tableFile.readUint32LE();
|
|
} else {
|
|
_cowHeaderSize = _cowFile.readUint32LE();
|
|
_cowHeader = (uint32 *)malloc(_cowHeaderSize);
|
|
if (_cowHeaderSize & 3)
|
|
error("Unexpected cow header size %d", _cowHeaderSize);
|
|
for (uint32 cnt = 0; cnt < (_cowHeaderSize / 4) - 1; cnt++)
|
|
_cowHeader[cnt] = _cowFile.readUint32LE();
|
|
_currentCowFile = SwordEngine::_systemVars.currentCD;
|
|
}
|
|
} else
|
|
warning("Sound::initCowSystem: Can't open SPEECH%d.CLU", SwordEngine::_systemVars.currentCD);
|
|
}
|
|
|
|
void Sound::closeCowSystem() {
|
|
_cowFile.close();
|
|
free(_cowHeader);
|
|
_cowHeader = nullptr;
|
|
_currentCowFile = 0;
|
|
}
|
|
|
|
int32 Sound::checkSampleStatus(int32 id) {
|
|
Common::StackLock lock(_soundMutex);
|
|
|
|
for (int i = 0; i < MAX_FX; i++) {
|
|
if (_fxSampleId[i] == id) {
|
|
if (!_mixer->isSoundHandleActive(_hSampleFX[i]) && (_fxSampleBusy[i]) && (!_fxPaused[i])) {
|
|
_fxSampleBusy[i] = false;
|
|
return S_STATUS_FINISHED;
|
|
} else {
|
|
return S_STATUS_RUNNING;
|
|
}
|
|
}
|
|
}
|
|
|
|
return S_STATUS_RUNNING;
|
|
}
|
|
|
|
int32 Sound::checkSpeechStatus() {
|
|
Common::StackLock lock(_soundMutex);
|
|
|
|
if (!_speechSampleBusy || _speechPaused)
|
|
return S_STATUS_FINISHED;
|
|
|
|
if (!_mixer->isSoundHandleActive(_hSampleSpeech)) {
|
|
_speechSampleBusy = 0;
|
|
restoreMusicVolume();
|
|
return S_STATUS_FINISHED;
|
|
}
|
|
|
|
return S_STATUS_RUNNING;
|
|
}
|
|
|
|
void Sound::playSpeech() {
|
|
Common::StackLock lock(_soundMutex);
|
|
|
|
_speechLipsyncCounter = 0;
|
|
|
|
if (_speechSampleBusy)
|
|
stopSpeech();
|
|
|
|
_speechSampleBusy = true;
|
|
|
|
Audio::SeekableAudioStream *stream = Audio::makeRawStream(
|
|
(byte *)_speechSample, _speechSize, 11025, SPEECH_FLAGS, DisposeAfterUse::NO);
|
|
_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_hSampleSpeech, stream);
|
|
|
|
byte speechVolume = clampVolume(2 * (4 * (_volSpeech[0] + _volSpeech[1])));
|
|
_mixer->setChannelVolume(_hSampleSpeech, speechVolume);
|
|
|
|
int pan = 64 + (4 * ((int32)_volSpeech[1] - (int32)_volSpeech[0]));
|
|
_mixer->setChannelBalance(_hSampleSpeech, scalePan(pan));
|
|
|
|
reduceMusicVolume();
|
|
}
|
|
|
|
void Sound::stopSpeech() {
|
|
Common::StackLock lock(_soundMutex);
|
|
|
|
if (_mixer->isSoundHandleActive(_hSampleSpeech)) {
|
|
_mixer->stopHandle(_hSampleSpeech);
|
|
_speechSampleBusy = false;
|
|
restoreMusicVolume();
|
|
}
|
|
}
|
|
|
|
static void soundCallback(void *refCon) {
|
|
Sound *snd = (Sound *)refCon;
|
|
Common::StackLock lock(snd->_soundMutex);
|
|
|
|
// Originally the code here had handling of fading flags for
|
|
// the master volume and the music volume (the latter only
|
|
// for fade-ups). They are omitted here as they are not used
|
|
// anywhere...
|
|
|
|
if (snd->_fxFadingFlag) {
|
|
snd->_fxCount += 1;
|
|
if (snd->_fxCount > 128 / snd->_fxFadingRate) {
|
|
snd->_fxFadingFlag = 0;
|
|
} else {
|
|
if (snd->_fxFadingFlag == 1) {
|
|
// Fade the volume up...
|
|
snd->_fxFadeVolume[0] = 8 * snd->_volFX[0] * snd->_fxCount * snd->_fxFadingRate / 128;
|
|
snd->_fxFadeVolume[1] = 8 * snd->_volFX[1] * snd->_fxCount * snd->_fxFadingRate / 128;
|
|
} else {
|
|
// Fade the volume down...
|
|
snd->_fxFadeVolume[0] = 8 * snd->_volFX[0] - (8 * snd->_volFX[0] * snd->_fxCount * snd->_fxFadingRate / 128);
|
|
snd->_fxFadeVolume[1] = 8 * snd->_volFX[1] - (8 * snd->_volFX[1] * snd->_fxFadingRate * snd->_fxCount / 128);
|
|
}
|
|
|
|
for (int i = 0; i < MAX_FX; i++) {
|
|
if (snd->_fxSampleBusy[i]) {
|
|
int32 targetVolume = (snd->_fxFadeVolume[0] + snd->_fxFadeVolume[1]) / 2;
|
|
|
|
// Multiplying by 2 because Miles Sound System uses 0-127 and we use 0-255
|
|
snd->setFXVolume(snd->clampVolume(targetVolume * 2), i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sound::installFadeTimer() {
|
|
_vm->getTimerManager()->installTimerProc(&soundCallback, 1000000 / TIMER_RATE, this, "AILFadeTimer");
|
|
}
|
|
|
|
void Sound::uninstallFadeTimer() {
|
|
_vm->getTimerManager()->removeTimerProc(&soundCallback);
|
|
}
|
|
|
|
void Sound::setFXVolume(byte targetVolume, int handleIdx) {
|
|
_mixer->setChannelVolume(_hSampleFX[handleIdx], targetVolume);
|
|
}
|
|
|
|
void Sound::playSample(int32 fxNo) {
|
|
uint8 *samplePtr;
|
|
uint32 vol[2] = { 0, 0 };
|
|
int32 screen = Logic::_scriptVars[SCREEN];
|
|
|
|
samplePtr = (uint8 *)_resMan->fetchRes(getSampleId(fxNo));
|
|
|
|
for (int i = 0; i < MAX_ROOMS_PER_FX; i++) {
|
|
if (_fxList[fxNo].roomVolList[i].roomNo != 0) {
|
|
if ((_fxList[fxNo].roomVolList[i].roomNo == screen) ||
|
|
(_fxList[fxNo].roomVolList[i].roomNo == -1)) { // -1 indicates 'all rooms'
|
|
vol[0] = (_fxList[fxNo].roomVolList[i].leftVol);
|
|
vol[1] = (_fxList[fxNo].roomVolList[i].rightVol);
|
|
|
|
debug(5, "Sound::playSample(): fxNo=%d, vol[0]=%d, vol[1]=%d)",fxNo,vol[0],vol[1]);
|
|
playFX(fxNo, _fxList[fxNo].type, samplePtr, vol);
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sound::stopSample(int32 fxNo) {
|
|
stopFX(fxNo);
|
|
}
|
|
|
|
bool Sound::prepareMusicStreaming(const Common::Path &filename, int newHandleId, int32 tuneId, uint32 volume, int8 pan, MusCompMode assignedMode) {
|
|
int sampleRate = DEFAULT_SAMPLE_RATE;
|
|
WaveHeader wavHead;
|
|
bool isStereo = false;
|
|
|
|
if (filename.empty())
|
|
return false;
|
|
|
|
if (!_musicFile[newHandleId].open(filename)) {
|
|
debug(5, "Sound::streamMusicFile(): couldn't find file %s, bailing out...", filename.toString().c_str());
|
|
return false;
|
|
}
|
|
|
|
delete _compressedMusicStream[newHandleId];
|
|
|
|
if (assignedMode == MusWav) {
|
|
if (_musicFile[newHandleId].read(&wavHead, sizeof(WaveHeader)) != sizeof(WaveHeader)) {
|
|
debug(5, "Sound::streamMusicFile(): couldn't read from file %s, bailing out...", filename.toString().c_str());
|
|
_musicFile[newHandleId].close();
|
|
return false;
|
|
}
|
|
|
|
sampleRate = wavHead.dwSamplesPerSec;
|
|
}
|
|
#ifdef USE_FLAC
|
|
else if (assignedMode == MusFLAC) {
|
|
_compressedMusicStream[newHandleId] = Audio::makeFLACStream(&_musicFile[newHandleId], DisposeAfterUse::NO);
|
|
sampleRate = _compressedMusicStream[newHandleId]->getRate();
|
|
}
|
|
#endif
|
|
#ifdef USE_VORBIS
|
|
else if (assignedMode == MusVorbis) {
|
|
_compressedMusicStream[newHandleId] = Audio::makeVorbisStream(&_musicFile[newHandleId], DisposeAfterUse::NO);
|
|
sampleRate = _compressedMusicStream[newHandleId]->getRate();
|
|
}
|
|
#endif
|
|
#ifdef USE_MAD
|
|
else if (assignedMode == MusMP3) {
|
|
_compressedMusicStream[newHandleId] = Audio::makeMP3Stream(&_musicFile[newHandleId], DisposeAfterUse::NO);
|
|
sampleRate = _compressedMusicStream[newHandleId]->getRate();
|
|
}
|
|
#endif
|
|
else if (assignedMode == MusAif) {
|
|
_compressedMusicStream[newHandleId] = Audio::makeAIFFStream(&_musicFile[newHandleId], DisposeAfterUse::NO);
|
|
sampleRate = _compressedMusicStream[newHandleId]->getRate();
|
|
} else if (assignedMode == MusPSX) {
|
|
Common::File tableFile;
|
|
if (!tableFile.open("tunes.tab")) {
|
|
debug(5, "Sound::streamMusicFile(): couldn't open the tunes.tab file, bailing out...");
|
|
return false;
|
|
}
|
|
|
|
// The PSX demo has a broken/truncated tunes.tab. So we check here
|
|
// that the offset is not beyond the end of the file.
|
|
int32 tableOffset = (tuneId - 1) * 8;
|
|
if (tableOffset >= tableFile.size())
|
|
return false;
|
|
tableFile.seek(tableOffset, SEEK_SET);
|
|
uint32 offset = tableFile.readUint32LE() * 0x800;
|
|
uint32 size = tableFile.readUint32LE();
|
|
|
|
tableFile.close();
|
|
|
|
// Because of broken tunes.dat/tab in psx demo,
|
|
// also check that tune offset is not over file size
|
|
if ((size != 0) && (size != 0xffffffff) && ((int32)(offset + size) <= _musicFile[newHandleId].size())) {
|
|
_musicFile[newHandleId].seek(offset, SEEK_SET);
|
|
_compressedMusicStream[newHandleId] = Audio::makeXAStream(_musicFile[newHandleId].readStream(size), DEFAULT_SAMPLE_RATE);
|
|
}
|
|
}
|
|
|
|
if (assignedMode != MusWav && !_compressedMusicStream[newHandleId]) {
|
|
debug(5, "Sound::streamMusicFile(): couldn't process compressed file %s, bailing out...", filename.toString().c_str());
|
|
_musicFile[newHandleId].close();
|
|
return false;
|
|
}
|
|
|
|
_musicOutputStream[newHandleId] = Audio::makeQueuingAudioStream(sampleRate, isStereo);
|
|
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_hSampleMusic[newHandleId], _musicOutputStream[newHandleId]);
|
|
|
|
_mixer->setChannelRate(_hSampleMusic[newHandleId], sampleRate);
|
|
_mixer->setChannelVolume(_hSampleMusic[newHandleId], clampVolume((int32)volume));
|
|
_mixer->setChannelBalance(_hSampleMusic[newHandleId], pan);
|
|
|
|
_musicStreamPlaying[newHandleId] = true;
|
|
_musicStreamFormat[newHandleId] = assignedMode;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Sound::streamMusicFile(int32 tuneId, int32 looped) {
|
|
int32 oldHandleId, newHandleId;
|
|
|
|
Common::File tmp;
|
|
Common::String filename(_tuneList[tuneId]);
|
|
|
|
MusCompMode assignedMode = MusWav;
|
|
if (tmp.exists(Common::Path(filename + ".wav"))) {
|
|
filename = filename + ".wav";
|
|
assignedMode = MusWav;
|
|
} else if (SwordEngine::isPsx() && tmp.exists("tunes.dat") && tmp.exists("tunes.tab")) {
|
|
filename = "tunes.dat";
|
|
assignedMode = MusPSX;
|
|
} else if (tmp.exists(Common::Path(filename + ".fla"))) {
|
|
filename = filename + ".fla";
|
|
assignedMode = MusFLAC;
|
|
} else if (tmp.exists(Common::Path(filename + ".ogg"))) {
|
|
filename = filename + ".ogg";
|
|
assignedMode = MusVorbis;
|
|
} else if (tmp.exists(Common::Path(filename + ".mp3"))) {
|
|
filename = filename + ".mp3";
|
|
assignedMode = MusMP3;
|
|
} else if (tmp.exists(Common::Path(filename + ".flac"))) {
|
|
filename = filename + ".flac";
|
|
assignedMode = MusFLAC;
|
|
} else if (tmp.exists(Common::Path(filename + ".aif"))) {
|
|
filename = filename + ".aif";
|
|
assignedMode = MusAif;
|
|
} else {
|
|
filename = "";
|
|
}
|
|
|
|
newHandleId = 0;
|
|
|
|
if ((_musicStreamPlaying[0]) || (_musicStreamPlaying[1])) {
|
|
// In this case at least one of the music streams is already
|
|
// busy so we still attempt to find a free one...
|
|
if (_musicStreamPlaying[0])
|
|
newHandleId = 1;
|
|
oldHandleId = 1 - newHandleId;
|
|
|
|
_streamLoopingFlag[newHandleId] = looped;
|
|
|
|
_musicStreamFading[oldHandleId] = -12;
|
|
_musicStreamFading[newHandleId] = 1;
|
|
|
|
if (_musicStreamPlaying[newHandleId]) {
|
|
// Whoops, they are BOTH busy! Let's kill the older one...
|
|
_mixer->stopHandle(_hSampleMusic[newHandleId]);
|
|
_musicFile[newHandleId].close();
|
|
|
|
bool success = prepareMusicStreaming(Common::Path(filename), newHandleId, tuneId,
|
|
2 * (2 * (_volMusic[0] + _volMusic[1])),
|
|
scalePan(64 + (4 * (_volMusic[1] - _volMusic[0]))),
|
|
assignedMode);
|
|
|
|
if (success)
|
|
debug(5, "Sound::streamMusicFile(): interrupting sound in handle %d to play %s", newHandleId, filename.c_str());
|
|
|
|
return;
|
|
} else {
|
|
// All good! We got the non-busy one :-)
|
|
bool success = prepareMusicStreaming(Common::Path(filename), newHandleId, tuneId,
|
|
0,
|
|
scalePan(64 + (4 * (_volMusic[1] - _volMusic[0]))),
|
|
assignedMode);
|
|
|
|
if (success)
|
|
debug(5, "Sound::streamMusicFile(): playing sound %s in handle %d with other handle busy", filename.c_str(), newHandleId);
|
|
}
|
|
} else {
|
|
// No streams are busy, let's go!
|
|
bool success = prepareMusicStreaming(Common::Path(filename), newHandleId, tuneId,
|
|
2 * (3 * (_volMusic[0] + _volMusic[1])),
|
|
scalePan(64 + (4 * (_volMusic[1] - _volMusic[0]))),
|
|
assignedMode);
|
|
|
|
if (success)
|
|
debug(5, "Sound::streamMusicFile(): playing sound %s in handle %d", filename.c_str(), newHandleId);
|
|
}
|
|
|
|
_streamLoopingFlag[newHandleId] = looped;
|
|
}
|
|
|
|
void Sound::updateMusicStreaming() {
|
|
Common::StackLock lock(_soundMutex);
|
|
|
|
// Within this function we make sure to fade music streams and
|
|
// stop them whenever they are either finished or at zero volume
|
|
for (int i = 0; i < MAX_MUSIC; i++) {
|
|
if ((_musicStreamPlaying[i]) && (!_musicPaused[i])) {
|
|
if (_musicStreamFading[i]) {
|
|
if (_crossFadeIncrement) {
|
|
_crossFadeIncrement = false;
|
|
|
|
if (_musicStreamFading[i] < 0) {
|
|
debug("Sound::updateMusicStreaming(): Fading %s to %d", _musicFile[i].getName(),
|
|
2 * (((0 - _musicStreamFading[i]) * 3 * (_volMusic[0] + _volMusic[1])) / 16));
|
|
_mixer->setChannelVolume(_hSampleMusic[i],
|
|
clampVolume(2 * (((0 - _musicStreamFading[i]) * 3 * (_volMusic[0] + _volMusic[1])) / 16)));
|
|
|
|
_musicStreamFading[i] += 1;
|
|
if (_musicStreamFading[i] == 0) {
|
|
_mixer->setChannelVolume(_hSampleMusic[i], 0);
|
|
_musicOutputStream[i]->finish();
|
|
_musicOutputStream[i] = nullptr;
|
|
|
|
_mixer->stopHandle(_hSampleMusic[i]);
|
|
_musicFile[i].close();
|
|
|
|
_musicStreamPlaying[i] = false;
|
|
}
|
|
} else {
|
|
debug("Sound::updateMusicStreaming(): Fading %s to %d", _musicFile[i].getName(),
|
|
2 * ((_musicStreamFading[i] * 3 * (_volMusic[0] + _volMusic[1])) / 16));
|
|
_mixer->setChannelVolume(_hSampleMusic[i],
|
|
clampVolume(2 * ((_musicStreamFading[i] * 3 * (_volMusic[0] + _volMusic[1])) / 16)));
|
|
|
|
_musicStreamFading[i] += 1;
|
|
if (_musicStreamFading[i] == 17) {
|
|
_musicStreamFading[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_musicFile[i].isOpen())
|
|
serveSample(&_musicFile[i], i);
|
|
|
|
if (!_mixer->isSoundHandleActive(_hSampleMusic[i]) ||
|
|
(_musicOutputStream[i] && _musicOutputStream[i]->endOfData())) {
|
|
_musicStreamPlaying[i] = false;
|
|
|
|
if (_musicFile[i].isOpen())
|
|
_musicFile[i].close();
|
|
|
|
if (_musicOutputStream[i]) {
|
|
_musicOutputStream[i]->finish();
|
|
_musicOutputStream[i] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sound::serveSample(Common::File *file, int32 i) {
|
|
int32 len;
|
|
int32 nominalSize = MUSIC_BUFFER_SIZE;
|
|
byte *buf = nullptr;
|
|
|
|
if (!_musicPaused[i]) {
|
|
if (_musicOutputStream[i]->numQueuedStreams() < 4) {
|
|
if (_musicStreamFormat[i] == MusWav) {
|
|
buf = (byte *)malloc(nominalSize);
|
|
if (!buf) {
|
|
warning("Sound::serveSample(): Couldn't allocate memory for streaming file %s", file->getName());
|
|
return;
|
|
}
|
|
|
|
len = file->read(buf, nominalSize);
|
|
if (len < nominalSize) {
|
|
if (_streamLoopingFlag[i]) {
|
|
file->seek(44, SEEK_SET);
|
|
file->read(buf + len, nominalSize - len);
|
|
len = nominalSize;
|
|
debug(5, "Sound::serveSample(): Looping music file %s", file->getName());
|
|
}
|
|
}
|
|
} else {
|
|
buf = (byte *)malloc(nominalSize * 2);
|
|
if (!buf) {
|
|
warning("Sound::serveSample(): Couldn't allocate memory for streaming file %s", file->getName());
|
|
return;
|
|
}
|
|
|
|
len = _compressedMusicStream[i]->readBuffer((int16 *)buf, nominalSize);
|
|
if (len < nominalSize) {
|
|
if (_streamLoopingFlag[i]) {
|
|
_compressedMusicStream[i]->rewind();
|
|
_compressedMusicStream[i]->readBuffer((int16 *)buf + len, nominalSize - len);
|
|
len = nominalSize;
|
|
debug(5, "Sound::serveSample(): Looping music file %s", file->getName());
|
|
} else {
|
|
Common::String fname(file->getName());
|
|
if (!fname.empty())
|
|
debug(5, "Sound::serveSample(): Finished feeding music file %s", file->getName());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_musicStreamFormat[i] != MusWav)
|
|
len *= 2;
|
|
|
|
_musicOutputStream[i]->queueBuffer(buf, len, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sound::playFX(int32 fxID, int32 type, uint8 *wavData, uint32 vol[2]) {
|
|
Common::StackLock lock(_soundMutex);
|
|
|
|
int32 v0, v1;
|
|
// Search through the FX sample handles for a free slot...
|
|
for (int i = 0; i < MAX_FX; i++) {
|
|
if (!_fxSampleBusy[i]) {
|
|
// Found the handle! Now setup and play the sound...
|
|
_fxSampleBusy[i] = true;
|
|
_fxSampleId[i] = fxID;
|
|
|
|
Audio::AudioStream *stream = nullptr;
|
|
|
|
if (SwordEngine::isPsx()) {
|
|
uint32 size = READ_LE_UINT32(wavData);
|
|
stream = Audio::makeLoopingAudioStream(
|
|
Audio::makeXAStream(new Common::MemoryReadStream(wavData + 4, size - 4), 11025),
|
|
(type == FX_LOOP) ? 0 : 1);
|
|
} else {
|
|
uint32 size = READ_LE_UINT32(wavData + 0x28);
|
|
uint8 flags;
|
|
if (READ_LE_UINT16(wavData + 0x22) == 16)
|
|
flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
|
|
else
|
|
flags = Audio::FLAG_UNSIGNED;
|
|
if (READ_LE_UINT16(wavData + 0x16) == 2)
|
|
flags |= Audio::FLAG_STEREO;
|
|
|
|
stream = Audio::makeLoopingAudioStream(
|
|
Audio::makeRawStream(wavData + 0x2C, size, 11025, flags, DisposeAfterUse::NO),
|
|
(type == FX_LOOP) ? 0 : 1);
|
|
}
|
|
|
|
if (stream) {
|
|
v0 = _volFX[0] * vol[0];
|
|
v1 = _volFX[1] * vol[1];
|
|
|
|
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_hSampleFX[i], stream, -1, 0);
|
|
_mixer->setChannelVolume(_hSampleFX[i], clampVolume(2 * ((v0 + v1) / 8)));
|
|
_mixer->setChannelBalance(_hSampleFX[i], scalePan(64 + ((v1 - v0) / 4)));
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sound::stopFX(int32 fxID) {
|
|
Common::StackLock lock(_soundMutex);
|
|
for (int i = 0; i < MAX_FX; i++) {
|
|
if (_fxSampleId[i] == fxID) {
|
|
if (_mixer->isSoundHandleActive(_hSampleFX[i])) {
|
|
_mixer->stopHandle(_hSampleFX[i]);
|
|
_fxSampleBusy[i] = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sound::clearAllFx() {
|
|
// Remove them starting from the end...
|
|
for (int j = _endOfQueue - 1; j >= 0; j--) {
|
|
// Check if the sample has finished playing and
|
|
// if not, stop it manually...
|
|
if (checkSampleStatus(_fxQueue[j].id) == S_STATUS_RUNNING)
|
|
stopFX(_fxQueue[j].id);
|
|
|
|
removeFromQueue(_fxQueue[j].id);
|
|
}
|
|
|
|
_endOfQueue = 0;
|
|
}
|
|
|
|
void Sound::fadeMusicDown(int32 rate) {
|
|
Common::StackLock lock(_soundMutex);
|
|
_musicStreamFading[1 - _musicStreamPlaying[0]] = -12;
|
|
}
|
|
|
|
void Sound::fadeFxDown(int32 rate) {
|
|
Common::StackLock lock(_soundMutex);
|
|
_fxFadingFlag = -1;
|
|
_fxFadingRate = 2 * rate;
|
|
_fxCount = 0;
|
|
}
|
|
|
|
void Sound::fadeFxUp(int32 rate) {
|
|
Common::StackLock lock(_soundMutex);
|
|
_fxFadingFlag = 1;
|
|
_fxFadingRate = 2 * rate;
|
|
_fxCount = 0;
|
|
}
|
|
|
|
int32 Sound::getSpeechSize(byte *compData, uint32 compSize) {
|
|
if ((_cowMode == CowWave) || (_cowMode == CowDemo)) {
|
|
WaveHeader *waveHeader = (WaveHeader *)compData;
|
|
|
|
return (FROM_LE_32(waveHeader->riffSize) + 8) - sizeof(WaveHeader);
|
|
} else {
|
|
Common::MemoryReadStream memStream(compData, compSize);
|
|
Audio::RewindableAudioStream *stream = nullptr;
|
|
|
|
if (_cowMode == CowPSX) {
|
|
stream = Audio::makeXAStream(&memStream, 11025);
|
|
}
|
|
#ifdef USE_FLAC
|
|
else if (_cowMode == CowFLAC) {
|
|
stream = Audio::makeFLACStream(&memStream, DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
#ifdef USE_VORBIS
|
|
else if (_cowMode == CowVorbis) {
|
|
stream = Audio::makeVorbisStream(&memStream, DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
#ifdef USE_MAD
|
|
else if (_cowMode == CowMP3) {
|
|
stream = Audio::makeMP3Stream(&memStream, DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
|
|
if (stream) {
|
|
byte tmpBuffer[1000 * 2];
|
|
int32 finalSize = 0;
|
|
while (!stream->endOfData())
|
|
finalSize += stream->readBuffer((int16 *)tmpBuffer, 1000) * 2;
|
|
|
|
return finalSize;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Sound::reduceMusicVolume() {
|
|
Common::StackLock lock(_soundMutex);
|
|
_musicFadeVolume[0] = _volMusic[0] * MUSIC_UNDERSCORE / 100;
|
|
_musicFadeVolume[1] = _volMusic[0] * MUSIC_UNDERSCORE / 100; // We are explicitly accessing _volMusic[0] again
|
|
|
|
// Multiplying by 2 because Miles Sound System uses 0-127 and we use 0-255
|
|
_mixer->setChannelVolume(_hSampleMusic[0], clampVolume(2 * ((_musicFadeVolume[0] + _musicFadeVolume[1]) * 3)));
|
|
}
|
|
|
|
void Sound::restoreMusicVolume() {
|
|
Common::StackLock lock(_soundMutex);
|
|
|
|
// Multiplying by 2 because Miles Sound System uses 0-127 and we use 0-255
|
|
_mixer->setChannelVolume(_hSampleMusic[0], clampVolume(2 * ((_volMusic[0] + _volMusic[1]) * 3)));
|
|
}
|
|
|
|
void Sound::setCrossFadeIncrement() {
|
|
_crossFadeIncrement = true;
|
|
}
|
|
|
|
void Sound::pauseSpeech() {
|
|
if ((_speechSampleBusy) && (!_speechPaused)) {
|
|
_speechPaused = true;
|
|
_mixer->pauseHandle(_hSampleSpeech, true);
|
|
}
|
|
}
|
|
|
|
void Sound::unpauseSpeech() {
|
|
if ((_speechSampleBusy) && (_speechPaused)) {
|
|
_speechPaused = false;
|
|
_mixer->pauseHandle(_hSampleSpeech, false);
|
|
}
|
|
}
|
|
|
|
void Sound::pauseMusic() {
|
|
Common::StackLock lock(_soundMutex);
|
|
for (int i = 0; i < MAX_MUSIC; i++) {
|
|
if (_musicStreamPlaying[i]) {
|
|
_musicPaused[i] = true;
|
|
_mixer->pauseHandle(_hSampleMusic[i], true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sound::unpauseMusic() {
|
|
Common::StackLock lock(_soundMutex);
|
|
for (int i = 0; i < MAX_MUSIC; i++) {
|
|
if (_musicPaused[i]) {
|
|
_mixer->pauseHandle(_hSampleMusic[i], false);
|
|
_musicPaused[i] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sound::pauseFx() {
|
|
Common::StackLock lock(_soundMutex);
|
|
for (int i = 0; i < MAX_FX; i++) {
|
|
if (_fxSampleBusy[i]) {
|
|
_mixer->pauseHandle(_hSampleFX[i], true);
|
|
_fxPaused[i] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sound::unpauseFx() {
|
|
Common::StackLock lock(_soundMutex);
|
|
for (int i = 0; i < MAX_FX; i++) {
|
|
if (_fxPaused[i]) {
|
|
_mixer->pauseHandle(_hSampleFX[i], false);
|
|
_fxPaused[i] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void getConfigVolumes(uint32 volL, uint32 volR, int &balance, int &volume) {
|
|
// Calculate the balance
|
|
if (volL + volR == 0) {
|
|
balance = 50;
|
|
} else {
|
|
balance = (int)(100.0f * volL / (volL + volR) + 0.5f);
|
|
}
|
|
|
|
// Calculate and scale the volume to the 0-255 range
|
|
volume = (int)(((volL + volR) * 255.0f / 32) + 0.5f);
|
|
volume = CLIP<int>(((volL + volR) * 255 / 32), 0, 255);
|
|
}
|
|
|
|
static void getGameVolumes(int balance, int volume, uint32 &volL, uint32 &volR) {
|
|
volume = CLIP<int>(volume, 0, 255);
|
|
|
|
int totalVolume = (int)(volume * 32.0f / 255 + 0.5f);
|
|
|
|
if (balance == 50) {
|
|
volL = totalVolume / 2;
|
|
volR = volL;
|
|
return;
|
|
}
|
|
|
|
volL = (uint32)(totalVolume * (balance / 100.0f) + 0.5f);
|
|
volR = totalVolume - volL;
|
|
}
|
|
|
|
void Sound::getVolumes() {
|
|
int musicVol = ConfMan.getInt("music_volume");
|
|
int sfxVol = ConfMan.getInt("sfx_volume");
|
|
int speechVol = ConfMan.getInt("speech_volume");
|
|
|
|
int musicBal = 50;
|
|
if (ConfMan.hasKey("music_balance")) {
|
|
musicBal = CLIP(ConfMan.getInt("music_balance"), 0, 100);
|
|
}
|
|
|
|
int speechBal = 50;
|
|
if (ConfMan.hasKey("speech_balance")) {
|
|
speechBal = CLIP(ConfMan.getInt("speech_balance"), 0, 100);
|
|
}
|
|
|
|
int sfxBal = 50;
|
|
if (ConfMan.hasKey("sfx_balance")) {
|
|
sfxBal = CLIP(ConfMan.getInt("sfx_balance"), 0, 100);
|
|
}
|
|
|
|
getGameVolumes(musicBal, musicVol, _volMusic[0], _volMusic[1]);
|
|
getGameVolumes(speechBal, speechVol, _volSpeech[0], _volSpeech[1]);
|
|
getGameVolumes(sfxBal, sfxVol, _volFX[0], _volFX[1]);
|
|
|
|
if (ConfMan.getBool("mute")) {
|
|
_volSpeech[0] = 0;
|
|
_volSpeech[1] = 0;
|
|
}
|
|
|
|
SwordEngine::_systemVars.showText = ConfMan.getBool("subtitles");
|
|
|
|
if (_volSpeech[0] + _volSpeech[1] == 0) {
|
|
SwordEngine::_systemVars.showText = true;
|
|
SwordEngine::_systemVars.playSpeech = false;
|
|
} else {
|
|
SwordEngine::_systemVars.playSpeech = true;
|
|
}
|
|
}
|
|
|
|
void Sound::setVolumes() {
|
|
int volume = 0;
|
|
int balance = 0;
|
|
|
|
getConfigVolumes(_volMusic[0], _volMusic[1], balance, volume);
|
|
if (volume != ConfMan.getInt("music_volume"))
|
|
ConfMan.setInt("music_volume", volume);
|
|
if (balance != ConfMan.getInt("music_balance"))
|
|
ConfMan.setInt("music_balance", balance);
|
|
|
|
getConfigVolumes(_volSpeech[0], _volSpeech[1], balance, volume);
|
|
if (volume != ConfMan.getInt("speech_volume"))
|
|
ConfMan.setInt("speech_volume", volume);
|
|
if (balance != ConfMan.getInt("speech_balance"))
|
|
ConfMan.setInt("speech_balance", balance);
|
|
|
|
getConfigVolumes(_volFX[0], _volFX[1], balance, volume);
|
|
if (volume != ConfMan.getInt("sfx_volume"))
|
|
ConfMan.setInt("sfx_volume", volume);
|
|
if (balance != ConfMan.getInt("sfx_balance"))
|
|
ConfMan.setInt("sfx_balance", balance);
|
|
|
|
if (SwordEngine::_systemVars.showText != ConfMan.getBool("subtitles"))
|
|
ConfMan.setBool("subtitles", SwordEngine::_systemVars.showText);
|
|
ConfMan.flushToDisk();
|
|
|
|
if (_volSpeech[0] + _volSpeech[1] == 0) {
|
|
SwordEngine::_systemVars.showText = true;
|
|
SwordEngine::_systemVars.playSpeech = false;
|
|
} else {
|
|
SwordEngine::_systemVars.playSpeech = true;
|
|
}
|
|
}
|
|
|
|
byte Sound::clampVolume(int32 volume) {
|
|
return (byte)CLIP<int32>(volume, 0, 255);
|
|
}
|
|
|
|
int8 Sound::scalePan(int pan) {
|
|
return (pan != 64) ? (int8)(2 * pan - 127) : 0;
|
|
}
|
|
|
|
} // End of namespace Sword1
|