mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-13 21:31:53 +00:00
437 lines
11 KiB
C++
437 lines
11 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* Additional copyright for this file:
|
|
* Copyright (C) 1994-1998 Revolution Software Ltd.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 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/>.
|
|
*/
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// BROKEN SWORD 2
|
|
//
|
|
// SOUND.CPP Contains the sound engine, fx & music functions
|
|
// Some very 'sound' code in here ;)
|
|
//
|
|
// (16Dec96 JEL)
|
|
//
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
#include "common/file.h"
|
|
#include "common/memstream.h"
|
|
#include "common/system.h"
|
|
#include "common/textconsole.h"
|
|
|
|
#include "sword2/sword2.h"
|
|
#include "sword2/defs.h"
|
|
#include "sword2/header.h"
|
|
#include "sword2/console.h"
|
|
#include "sword2/logic.h"
|
|
#include "sword2/resman.h"
|
|
#include "sword2/sound.h"
|
|
|
|
#include "audio/decoders/wave.h"
|
|
#include "audio/decoders/xa.h"
|
|
|
|
#define Debug_Printf _vm->_debugger->debugPrintf
|
|
|
|
namespace Sword2 {
|
|
|
|
Sound::Sound(Sword2Engine *vm) {
|
|
int i;
|
|
|
|
_vm = vm;
|
|
|
|
for (i = 0; i < FXQ_LENGTH; i++)
|
|
_fxQueue[i].resource = 0;
|
|
|
|
for (i = 0; i < MAXMUS; i++) {
|
|
_music[i] = NULL;
|
|
|
|
_musicFile[i].idxTab = NULL;
|
|
_musicFile[i].idxLen = 0;
|
|
_musicFile[i].fileSize = 0;
|
|
_musicFile[i].fileType = 0;
|
|
_musicFile[i].inUse = false;
|
|
|
|
_speechFile[i].idxTab = NULL;
|
|
_speechFile[i].idxLen = 0;
|
|
_speechFile[i].fileSize = 0;
|
|
_speechFile[i].fileType = 0;
|
|
_speechFile[i].inUse = false;
|
|
}
|
|
|
|
_speechPaused = false;
|
|
_musicPaused = false;
|
|
_fxPaused = false;
|
|
|
|
_speechMuted = false;
|
|
_musicMuted = false;
|
|
_fxMuted = false;
|
|
|
|
_reverseStereo = false;
|
|
|
|
_loopingMusicId = 0;
|
|
|
|
_mixBuffer = NULL;
|
|
_mixBufferLen = 0;
|
|
|
|
_vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
|
|
}
|
|
|
|
Sound::~Sound() {
|
|
_vm->_mixer->stopHandle(_mixerSoundHandle);
|
|
|
|
clearFxQueue(true);
|
|
stopMusic(true);
|
|
stopSpeech();
|
|
|
|
free(_mixBuffer);
|
|
|
|
for (int i = 0; i < MAXMUS; i++) {
|
|
if (_musicFile[i].file.isOpen())
|
|
_musicFile[i].file.close();
|
|
if (_speechFile[i].file.isOpen())
|
|
_speechFile[i].file.close();
|
|
|
|
free(_musicFile[i].idxTab);
|
|
free(_speechFile[i].idxTab);
|
|
}
|
|
}
|
|
|
|
void Sound::setReverseStereo(bool reverse) {
|
|
if (reverse != _reverseStereo) {
|
|
_reverseStereo = reverse;
|
|
|
|
for (int i = 0; i < FXQ_LENGTH; i++) {
|
|
if (!_fxQueue[i].resource)
|
|
continue;
|
|
|
|
_fxQueue[i].pan = -_fxQueue[i].pan;
|
|
_vm->_mixer->setChannelBalance(_fxQueue[i].handle, _fxQueue[i].pan);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop all sounds, close their resources and clear the FX queue. This is used
|
|
* when going from one room to another, among other things.
|
|
*/
|
|
|
|
void Sound::clearFxQueue(bool killMovieSounds) {
|
|
for (int i = 0; i < FXQ_LENGTH; i++) {
|
|
if (_fxQueue[i].resource) {
|
|
stopFx(i);
|
|
}
|
|
}
|
|
|
|
// We aren't just going to change rooms or anything like that, we are
|
|
// killing off resources (e.g. when restoring or restarting). We need
|
|
// to also kill any movie lead-in/out sounds.
|
|
|
|
if (killMovieSounds) {
|
|
_vm->_mixer->stopHandle(_leadInHandle);
|
|
_vm->_mixer->stopHandle(_leadOutHandle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process the FX queue. This function is called once every game cycle.
|
|
*/
|
|
|
|
void Sound::processFxQueue() {
|
|
for (int i = 0; i < FXQ_LENGTH; i++) {
|
|
if (!_fxQueue[i].resource)
|
|
continue;
|
|
|
|
switch (_fxQueue[i].type) {
|
|
case FX_RANDOM:
|
|
// 1 in 'delay' chance of this fx occurring
|
|
if (_vm->_rnd.getRandomNumber(_fxQueue[i].delay) == 0)
|
|
playFx(&_fxQueue[i]);
|
|
break;
|
|
case FX_SPOT:
|
|
if (_fxQueue[i].delay)
|
|
_fxQueue[i].delay--;
|
|
else {
|
|
playFx(&_fxQueue[i]);
|
|
_fxQueue[i].type = FX_SPOT2;
|
|
}
|
|
break;
|
|
case FX_LOOP:
|
|
playFx(&_fxQueue[i]);
|
|
_fxQueue[i].type = FX_LOOPING;
|
|
break;
|
|
case FX_SPOT2:
|
|
// Once the FX has finished remove it from the queue.
|
|
if (!_vm->_mixer->isSoundHandleActive(_fxQueue[i].handle)) {
|
|
_vm->_resman->closeResource(_fxQueue[i].resource);
|
|
_fxQueue[i].resource = 0;
|
|
}
|
|
break;
|
|
case FX_LOOPING:
|
|
// Once the looped FX has started we can ignore it,
|
|
// but we can't close it since the WAV data is in use.
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function is used by the cutscene player to play the movie lead-in/out.
|
|
* @param res The sound resource to play
|
|
* @param type Either kLeadInSound or kLeadOutSound
|
|
*/
|
|
|
|
void Sound::playMovieSound(int32 res, int type) {
|
|
Audio::SoundHandle *handle;
|
|
|
|
if (type == kLeadInSound)
|
|
handle = &_leadInHandle;
|
|
else
|
|
handle = &_leadOutHandle;
|
|
|
|
if (_vm->_mixer->isSoundHandleActive(*handle)) {
|
|
_vm->_mixer->stopHandle(*handle);
|
|
}
|
|
|
|
byte *data = _vm->_resman->openResource(res);
|
|
uint32 len = _vm->_resman->fetchLen(res);
|
|
|
|
assert(_vm->_resman->fetchType(data) == WAV_FILE);
|
|
|
|
// We want to close the resource right away, so to be safe we make a
|
|
// private copy of the sound;
|
|
byte *soundData = (byte *)malloc(len);
|
|
|
|
if (soundData) {
|
|
memcpy(soundData, data, len);
|
|
|
|
Common::MemoryReadStream *stream = new Common::MemoryReadStream(soundData, len, DisposeAfterUse::YES);
|
|
|
|
// In PSX version we have nothing to skip here, as data starts
|
|
// right away.
|
|
if (!Sword2Engine::isPsx()) {
|
|
stream->seek(ResHeader::size());
|
|
}
|
|
|
|
Audio::RewindableAudioStream *input = 0;
|
|
|
|
if (Sword2Engine::isPsx()) {
|
|
input = Audio::makeXAStream(stream, 11025);
|
|
} else {
|
|
input = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
|
|
}
|
|
|
|
_vm->_mixer->playStream(
|
|
Audio::Mixer::kMusicSoundType, handle, input,
|
|
-1, Audio::Mixer::kMaxChannelVolume, 0,
|
|
DisposeAfterUse::YES, false, isReverseStereo());
|
|
} else {
|
|
warning("Sound::playMovieSound: Could not allocate %d bytes\n", len);
|
|
}
|
|
|
|
_vm->_resman->closeResource(res);
|
|
}
|
|
|
|
void Sound::stopMovieSounds() {
|
|
if (_vm->_mixer->isSoundHandleActive(_leadInHandle)) {
|
|
_vm->_mixer->stopHandle(_leadInHandle);
|
|
}
|
|
if (_vm->_mixer->isSoundHandleActive(_leadOutHandle)) {
|
|
_vm->_mixer->stopHandle(_leadOutHandle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Queue a sound effect for playing later.
|
|
* @param res the sound resource number
|
|
* @param type the type of sound effect
|
|
* @param delay when to play the sound effect
|
|
* @param volume the sound effect volume (0 through 16)
|
|
* @param pan the sound effect panning (-16 through 16)
|
|
*/
|
|
|
|
void Sound::queueFx(int32 res, int32 type, int32 delay, int32 volume, int32 pan) {
|
|
if (_vm->_wantSfxDebug) {
|
|
const char *typeStr;
|
|
|
|
switch (type) {
|
|
case FX_SPOT:
|
|
typeStr = "SPOT";
|
|
break;
|
|
case FX_LOOP:
|
|
typeStr = "LOOPED";
|
|
break;
|
|
case FX_RANDOM:
|
|
typeStr = "RANDOM";
|
|
break;
|
|
default:
|
|
typeStr = "INVALID";
|
|
break;
|
|
}
|
|
|
|
debug(0, "SFX (sample=\"%s\", vol=%d, pan=%d, delay=%d, type=%s)", _vm->_resman->fetchName(res), volume, pan, delay, typeStr);
|
|
}
|
|
|
|
for (int i = 0; i < FXQ_LENGTH; i++) {
|
|
if (!_fxQueue[i].resource) {
|
|
byte *data = _vm->_resman->openResource(res);
|
|
|
|
// Check that we really have a WAV file here, alas this
|
|
// check is useless with psx demo game, because psx audio files
|
|
// are headerless and there is no way to check the type
|
|
if (!(Sword2Engine::isPsx() && (_vm->_features & ADGF_DEMO)))
|
|
assert(_vm->_resman->fetchType(data) == WAV_FILE);
|
|
|
|
uint32 len = _vm->_resman->fetchLen(res);
|
|
|
|
// Skip the header if using PC version
|
|
if (!Sword2Engine::isPsx())
|
|
len -= ResHeader::size();
|
|
|
|
if (type == FX_RANDOM) {
|
|
// For spot effects and loops the delay is the
|
|
// number of frames to wait. For random
|
|
// effects, however, it's the average number of
|
|
// seconds between playing the sound, so we
|
|
// have to multiply by the frame rate.
|
|
delay *= FRAMES_PER_SECOND;
|
|
}
|
|
|
|
volume = (volume * Audio::Mixer::kMaxChannelVolume) / 16;
|
|
pan = (pan * 127) / 16;
|
|
|
|
if (isReverseStereo())
|
|
pan = -pan;
|
|
|
|
_fxQueue[i].resource = res;
|
|
|
|
if (Sword2Engine::isPsx())
|
|
_fxQueue[i].data = data;
|
|
else
|
|
_fxQueue[i].data = data + ResHeader::size();
|
|
|
|
_fxQueue[i].len = len;
|
|
_fxQueue[i].delay = delay;
|
|
_fxQueue[i].volume = volume;
|
|
_fxQueue[i].pan = pan;
|
|
_fxQueue[i].type = type;
|
|
|
|
// Keep track of the index in the loop so that
|
|
// fnStopFx() can be used later to kill this sound.
|
|
// Mainly for FX_LOOP and FX_RANDOM.
|
|
|
|
_vm->_logic->writeVar(RESULT, i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
warning("No free slot in FX queue");
|
|
}
|
|
|
|
int32 Sound::playFx(FxQueueEntry *fx) {
|
|
return playFx(&fx->handle, fx->data, fx->len, fx->volume, fx->pan, (fx->type == FX_LOOP), Audio::Mixer::kSFXSoundType);
|
|
}
|
|
|
|
int32 Sound::playFx(Audio::SoundHandle *handle, byte *data, uint32 len, uint8 vol, int8 pan, bool loop, Audio::Mixer::SoundType soundType) {
|
|
if (_fxMuted)
|
|
return RD_OK;
|
|
|
|
if (_vm->_mixer->isSoundHandleActive(*handle))
|
|
return RDERR_FXALREADYOPEN;
|
|
|
|
Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, len);
|
|
Audio::RewindableAudioStream *input = 0;
|
|
|
|
if (Sword2Engine::isPsx())
|
|
input = Audio::makeXAStream(stream, 11025);
|
|
else
|
|
input = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
|
|
|
|
assert(input);
|
|
|
|
_vm->_mixer->playStream(soundType, handle,
|
|
Audio::makeLoopingAudioStream(input, loop ? 0 : 1),
|
|
-1, vol, pan, DisposeAfterUse::YES, false, isReverseStereo());
|
|
|
|
return RD_OK;
|
|
}
|
|
|
|
/**
|
|
* 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 i the index of the sound to close
|
|
*/
|
|
|
|
int32 Sound::stopFx(int32 i) {
|
|
if (!_fxQueue[i].resource)
|
|
return RDERR_FXNOTOPEN;
|
|
|
|
_vm->_mixer->stopHandle(_fxQueue[i].handle);
|
|
|
|
_vm->_resman->closeResource(_fxQueue[i].resource);
|
|
_fxQueue[i].resource = 0;
|
|
return RD_OK;
|
|
}
|
|
|
|
void Sound::printFxQueue() {
|
|
int freeSlots = 0;
|
|
|
|
for (int i = 0; i < FXQ_LENGTH; i++) {
|
|
if (_fxQueue[i].resource) {
|
|
const char *type;
|
|
|
|
switch (_fxQueue[i].type) {
|
|
case FX_SPOT:
|
|
type = "SPOT";
|
|
break;
|
|
case FX_LOOP:
|
|
type = "LOOP";
|
|
break;
|
|
case FX_RANDOM:
|
|
type = "RANDOM";
|
|
break;
|
|
case FX_SPOT2:
|
|
type = "SPOT2";
|
|
break;
|
|
case FX_LOOPING:
|
|
type = "LOOPING";
|
|
break;
|
|
default:
|
|
type = "UNKNOWN";
|
|
break;
|
|
}
|
|
|
|
Debug_Printf("%d: res: %d ('%s') %s (%d) delay: %d vol: %d pan: %d\n",
|
|
i, _fxQueue[i].resource,
|
|
_vm->_resman->fetchName(_fxQueue[i].resource),
|
|
type, _fxQueue[i].type, _fxQueue[i].delay,
|
|
_fxQueue[i].volume, _fxQueue[i].pan);
|
|
} else {
|
|
freeSlots++;
|
|
}
|
|
}
|
|
Debug_Printf("Free slots: %d\n", freeSlots);
|
|
}
|
|
|
|
} // End of namespace Sword2
|