Simplified the handling of sound effects. It's not necessary for the driver

to keep its own copy of the sound data. It could be even further simplified
(I don't really see any reason for having two different sound queues), but
I seem to have reached a point of stability here and I don't want to jinx
it by making further changes yet.

svn-id: r13705
This commit is contained in:
Torbjörn Andersson 2004-05-01 10:42:23 +00:00
parent e830d35a21
commit bc77ba431a
8 changed files with 178 additions and 266 deletions

3
NEWS
View File

@ -23,7 +23,8 @@ For a more comprehensive changelog for the latest experimental CVS code, see:
- ??? [TODO: Somebody of the Sword1 team please fill this in]
Sword2:
- Simplified the memory/resource management.
- Simplified the memory/resource management...
- ...which led to simplified sound effects handling
- Various minor bugfixes.
BASS

View File

@ -326,8 +326,8 @@ int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], byte *mus
// the animated cut-scenes, so this seems like a good place to close
// both of them.
_vm->_sound->closeFx(-1);
_vm->_sound->closeFx(-2);
_vm->_sound->stopFx(-1);
_vm->_sound->stopFx(-2);
return RD_OK;
#else
@ -478,8 +478,8 @@ int32 MoviePlayer::playDummy(const char *filename, MovieTextObject *text[], byte
// the animated cut-scenes, so this seems like a good place to close
// both of them.
_vm->_sound->closeFx(-1);
_vm->_sound->closeFx(-2);
_vm->_sound->stopFx(-1);
_vm->_sound->stopFx(-2);
return RD_OK;
}

View File

@ -1028,70 +1028,6 @@ bool Sound::isFxPlaying(int32 id) {
return _fx[i]._handle.isActive();
}
/**
* This function opens a sound effect ready for playing. A unique id should be
* passed in so that each effect can be referenced individually.
* @param id the unique sound id
* @param data the WAV data
* @warning Zero is not a valid id
*/
int32 Sound::openFx(int32 id, byte *data) {
if (!_soundOn)
return RD_OK;
if (id == 0)
return RDERR_INVALIDID;
if (getFxIndex(id) != MAXFX)
return RDERR_FXALREADYOPEN;
// Find a free slot
int32 fxi = getFxIndex(0);
if (fxi == MAXFX) {
warning("openFx: Running out of sound slots");
// There isn't any free sound handle 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;
}
}
_fx[fxi]._id = id;
_fx[fxi]._flags = SoundMixer::FLAG_16BITS | SoundMixer::FLAG_LITTLE_ENDIAN;
WavInfo wavInfo;
if (!getWavInfo(data, &wavInfo)) {
warning("openFx: Not a valida WAV file");
return RDERR_INVALIDWAV;
}
if (wavInfo.channels == 2)
_fx[fxi]._flags |= SoundMixer::FLAG_STEREO;
_fx[fxi]._rate = wavInfo.rate;
_fx[fxi]._bufSize = wavInfo.samples;
// Fill the speech buffer with data
free(_fx[fxi]._buf);
_fx[fxi]._buf = (uint16 *) malloc(_fx[fxi]._bufSize);
memcpy(_fx[fxi]._buf, wavInfo.data, _fx[fxi]._bufSize);
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
@ -1099,7 +1035,7 @@ int32 Sound::openFx(int32 id, byte *data) {
* @param id the id of the sound to close
*/
int32 Sound::closeFx(int32 id) {
int32 Sound::stopFx(int32 id) {
int i;
if (!_soundOn)
@ -1133,43 +1069,69 @@ int32 Sound::playFx(int32 id, byte *data, uint8 vol, int8 pan, uint8 type) {
return RD_OK;
byte volume = _fxMuted ? 0 : vol * _fxVol;
int8 p = _panTable[pan + 16];
int32 i, hr;
if (data) {
// All lead-ins and lead-outs I've heard are music, so we use
// the music volume setting for them.
// All lead-ins and lead-outs I've heard are music, so we use
// the music volume setting for them.
if (type == RDSE_FXLEADIN || type == RDSE_FXLEADOUT) {
id = (type == RDSE_FXLEADIN) ? -2 : -1;
volume = _musicMuted ? 0 : _musicVolTable[_musicVol];
}
hr = openFx(id, data);
if (hr != RD_OK)
return hr;
if (type == RDSE_FXLEADIN || type == RDSE_FXLEADOUT) {
id = (type == RDSE_FXLEADIN) ? -2 : -1;
volume = _musicMuted ? 0 : _musicVolTable[_musicVol];
}
i = getFxIndex(id);
WavInfo wavInfo;
if (i == MAXFX) {
if (data) {
warning("playFx(%d, %d, %d, %d) - Not found", id, vol, pan, type);
return RDERR_FXFUCKED;
} else {
warning("playFx(%d, %d, %d, %d) - Not open", id, vol, pan, type);
return RDERR_FXNOTOPEN;
}
if (!getWavInfo(data, &wavInfo)) {
warning("playFx: Not a valid WAV file");
return RDERR_INVALIDWAV;
}
int32 fxi = getFxIndex(id);
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;
}
}
_fx[fxi]._id = id;
}
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;
if (type == RDSE_FXLOOP)
_fx[i]._flags |= SoundMixer::FLAG_LOOP;
flags |= SoundMixer::FLAG_LOOP;
else
_fx[i]._flags &= ~SoundMixer::FLAG_LOOP;
flags &= ~SoundMixer::FLAG_LOOP;
_fx[i]._volume = vol;
_fx[fxi]._volume = vol;
_vm->_mixer->playRaw(&_fx[i]._handle, _fx[i]._buf, _fx[i]._bufSize, _fx[i]._rate, _fx[i]._flags, -1, volume, p);
int8 p = _panTable[pan + 16];
_vm->_mixer->playRaw(&_fx[fxi]._handle, wavInfo.data, wavInfo.samples, wavInfo.rate, flags, -1, volume, p);
return RD_OK;
}
@ -1177,12 +1139,8 @@ int32 Sound::playFx(int32 id, byte *data, uint8 vol, int8 pan, uint8 type) {
void Sound::stopFxHandle(int i) {
if (_fx[i]._id) {
_vm->_mixer->stopHandle(_fx[i]._handle);
free(_fx[i]._buf);
_fx[i]._id = 0;
_fx[i]._paused = false;
_fx[i]._flags = 0;
_fx[i]._bufSize = 0;
_fx[i]._buf = NULL;
}
}

View File

@ -44,10 +44,6 @@ struct FxHandle {
int32 _id;
bool _paused;
int8 _volume;
uint16 _rate;
uint32 _flags;
uint16 *_buf;
int32 _bufSize;
PlayingSoundHandle _handle;
};
@ -157,9 +153,8 @@ public:
void pauseFxForSequence(void);
void unpauseFx(void);
bool isFxPlaying(int32 id);
int32 openFx(int32 id, uint8 *data);
int32 closeFx(int32 id);
int32 playFx(int32 id, uint8 *data, uint8 vol, int8 pan, uint8 type);
int32 stopFx(int32 id);
void clearAllFx(void);
};

View File

@ -40,8 +40,9 @@ namespace Sword2 {
// is located in and the number within the cluster
// If 0, resouces are expelled immediately when they are closed. At the moment
// this causes the game to crash, which seems like a bug to me. In fact, it
// could be a clue to the mysterious and infrequent crashes...
// this causes the sound queue to run out of slots. My only theory is that it's
// a script that gets reloaded over and over. That'd clear its local variables
// which I guess may cause it to set up the sounds over and over.
#define CACHE_CLUSTERS 1
@ -768,6 +769,12 @@ void ResourceManager::removeAll(void) {
void ResourceManager::killAll(bool wantInfo) {
int nuked = 0;
// We need to clear the FX queue, because otherwise the sound system
// will still believe that the sound resources are in memory, and that
// it's ok to close them.
_vm->clearFxQueue();
for (uint i = 0; i < _totalResFiles; i++) {
// Don't nuke the global variables or the player object!
if (i == 1 || i == CUR_PLAYER_ID)

View File

@ -38,94 +38,100 @@
namespace Sword2 {
struct FxQueueEntry {
uint32 resource; // resource id of sample
byte *data; // pointer to WAV data
uint16 delay; // cycles to wait before playing (or 'random chance' if FX_RANDOM)
uint8 volume; // 0..16
int8 pan; // -16..16
uint8 type; // FX_SPOT, FX_RANDOM or FX_LOOP
};
// FIXME: Should be in one of the classes, I guess...
static FxQueueEntry fxQueue[FXQ_LENGTH];
/**
* Initialise the fxq by clearing all the entries.
* Initialise the FX queue by clearing all the entries. This is only used at
* the start of the game. Later when we need to clear the queue we must also
* stop the sound and close the resource.
*/
void Sword2Engine::initFxQueue(void) {
for (int i = 0; i < FXQ_LENGTH; i++)
_fxQueue[i].resource = 0;
fxQueue[i].resource = 0;
}
/**
* Process the fx queue once every game cycle
* Stop all sounds, close their resources and clear the FX queue.
*/
void Sword2Engine::clearFxQueue(void) {
for (int i = 0; i < FXQ_LENGTH; i++) {
if (fxQueue[i].resource) {
_sound->stopFx(i + 1);
_resman->closeResource(fxQueue[i].resource);
fxQueue[i].resource = 0;
}
}
}
/**
* Process the FX queue once every game cycle
*/
void Sword2Engine::processFxQueue(void) {
for (int i = 0; i < FXQ_LENGTH; i++) {
if (!_fxQueue[i].resource)
if (!fxQueue[i].resource)
continue;
switch (_fxQueue[i].type) {
switch (fxQueue[i].type) {
case FX_RANDOM:
// 1 in 'delay' chance of this fx occurring
if (_rnd.getRandomNumber(_fxQueue[i].delay) == 0)
if (_rnd.getRandomNumber(fxQueue[i].delay) == 0)
triggerFx(i);
break;
case FX_SPOT:
if (_fxQueue[i].delay)
_fxQueue[i].delay--;
if (fxQueue[i].delay)
fxQueue[i].delay--;
else {
triggerFx(i);
_fxQueue[i].type = FX_SPOT2;
fxQueue[i].type = FX_SPOT2;
}
break;
case FX_LOOP:
triggerFx(i);
fxQueue[i].type = FX_LOOPING;
break;
case FX_SPOT2:
// Once the Fx has finished remove it from the queue.
// Once the FX has finished remove it from the queue.
if (!_sound->isFxPlaying(i + 1)) {
_fxQueue[i].resource = 0;
_sound->closeFx(i + 1);
_sound->stopFx(i + 1);
_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;
}
}
}
void Sword2Engine::triggerFx(uint8 j) {
byte *data;
int32 id;
uint32 rv;
void Sword2Engine::triggerFx(uint8 i) {
int type;
id = (uint32) j + 1; // because 0 is not a valid id
if (_fxQueue[j].type == FX_SPOT) {
// load in the sample
data = _resman->openResource(_fxQueue[j].resource);
data += sizeof(StandardHeader);
// wav data gets copied to sound memory
rv = _sound->playFx(id, data, _fxQueue[j].volume, _fxQueue[j].pan, RDSE_FXSPOT);
// release the sample
_resman->closeResource(_fxQueue[j].resource);
} else {
// random & looped fx are already loaded into sound memory
// by fnPlayFx()
// - to be referenced by 'j', so pass NULL data
if (_fxQueue[j].type == FX_RANDOM) {
// Not looped
rv = _sound->playFx(id, NULL, _fxQueue[j].volume, _fxQueue[j].pan, RDSE_FXSPOT);
} else {
// Looped
rv = _sound->playFx(id, NULL, _fxQueue[j].volume, _fxQueue[j].pan, RDSE_FXLOOP);
}
}
if (fxQueue[i].type == FX_LOOP)
type = RDSE_FXLOOP;
else
type = RDSE_FXSPOT;
uint32 rv = _sound->playFx(i + 1, fxQueue[i].data, fxQueue[i].volume, fxQueue[i].pan, type);
if (rv)
debug(5, "SFX ERROR: playFx() returned %.8x", rv);
}
/**
* Stop all looped & random fx and clear the entire queue
*/
void Sword2Engine::clearFxQueue(void) {
// stop all fx & remove the samples from sound memory
_sound->clearAllFx();
// clean out the queue
initFxQueue();
}
void Sword2Engine::killMusic(void) {
_loopingMusicId = 0; // clear the 'looping' flag
_sound->stopMusic();
@ -159,15 +165,6 @@ int32 Logic::fnPlayFx(int32 *params) {
// .
// fnStopFx (fx_water);
uint8 j = 0;
byte *data;
uint32 id;
uint32 rv;
#ifdef _SWORD2_DEBUG
StandardHeader *header;
#endif
if (_vm->_wantSfxDebug) {
char type[10];
@ -191,76 +188,46 @@ int32 Logic::fnPlayFx(int32 *params) {
debug(0, "SFX (sample=\"%s\", vol=%d, pan=%d, delay=%d, type=%s)", _vm->fetchObjectName(params[0], buf), params[3], params[4], params[2], type);
}
while (j < FXQ_LENGTH && _vm->_fxQueue[j].resource != 0)
j++;
int i;
if (j == FXQ_LENGTH)
// Find a free slot in the FX queue
for (i = 0; i < FXQ_LENGTH; i++) {
if (!fxQueue[i].resource)
break;
}
if (i == FXQ_LENGTH) {
warning("No free slot in FX queue");
return IR_CONT;
_vm->_fxQueue[j].resource = params[0]; // wav resource id
_vm->_fxQueue[j].type = params[1]; // FX_SPOT, FX_LOOP or FX_RANDOM
if (_vm->_fxQueue[j].type == FX_RANDOM) {
// 'delay' param is the intended average no. seconds between
// playing this effect
_vm->_fxQueue[j].delay = params[2] * 12;
} else {
// FX_SPOT or FX_LOOP:
// 'delay' is no. frames to wait before playing
_vm->_fxQueue[j].delay = params[2];
}
_vm->_fxQueue[j].volume = params[3]; // 0..16
_vm->_fxQueue[j].pan = params[4]; // -16..16
fxQueue[i].resource = params[0];
fxQueue[i].type = params[1];
fxQueue[i].delay = params[2];
if (_vm->_fxQueue[j].type == FX_SPOT) {
// "pre-load" the sample; this gets it into memory
data = _vm->_resman->openResource(_vm->_fxQueue[j].resource);
#ifdef _SWORD2_DEBUG
header = (StandardHeader *) data;
if (header->fileType != WAV_FILE)
error("fnPlayFx given invalid resource");
#endif
// but then releases it to "age" out if the space is needed
_vm->_resman->closeResource(_vm->_fxQueue[j].resource);
} else {
// random & looped fx
id = (uint32) j + 1; // because 0 is not a valid id
// load in the sample
data = _vm->_resman->openResource(_vm->_fxQueue[j].resource);
#ifdef _SWORD2_DEBUG
header = (StandardHeader *) data;
if (header->fileType != WAV_FILE)
error("fnPlayFx given invalid resource");
#endif
data += sizeof(StandardHeader);
// copy it to sound memory, using position in queue as 'id'
rv = _vm->_sound->openFx(id, data);
if (rv)
debug(5, "SFX ERROR: openFx() returned %.8x", rv);
// release the sample
_vm->_resman->closeResource(_vm->_fxQueue[j].resource);
if (fxQueue[i].type == FX_RANDOM) {
// For spot effects and loops the dela 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.
fxQueue[i].delay *= 12;
}
if (_vm->_fxQueue[j].type == FX_LOOP) {
// play now, rather than in processFxQueue where it was
// getting played again & again!
_vm->triggerFx(j);
}
fxQueue[i].volume = params[3];
fxQueue[i].pan = params[4];
// in case we want to call fnStopFx() later, to kill this fx
// (mainly for FX_LOOP & FX_RANDOM)
byte *data = _vm->_resman->openResource(params[0]);
StandardHeader *header = (StandardHeader *) data;
_scriptVars[RESULT] = j;
assert(header->fileType == WAV_FILE);
fxQueue[i].data = data + sizeof(StandardHeader);
// 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.
_scriptVars[RESULT] = i;
return IR_CONT;
}
@ -270,7 +237,7 @@ int32 Logic::fnSoundFetch(int32 *params) {
}
/**
* Alter the volume and pan of a currently playing fx
* Alter the volume and pan of a currently playing FX
*/
int32 Logic::fnSetFxVolAndPan(int32 *params) {
@ -281,14 +248,12 @@ int32 Logic::fnSetFxVolAndPan(int32 *params) {
debug(5, "fnSetFxVolAndPan(%d, %d, %d)", params[0], params[1], params[2]);
// setFxIdVolumePan(int32 id, uint8 vol, uint8 pan);
// driver fx_id is 1 + <pos in queue>
_vm->_sound->setFxIdVolumePan(1 + params[0], params[1], params[2]);
_vm->_sound->setFxIdVolumePan(params[0] + 1, params[1], params[2]);
return IR_CONT;
}
/**
* Alter the volume of a currently playing fx
* Alter the volume of a currently playing FX
*/
int32 Logic::fnSetFxVol(int32 *params) {
@ -296,41 +261,33 @@ int32 Logic::fnSetFxVol(int32 *params) {
// fnPlayFx
// 1 new volume (0..16)
// SetFxIdVolume(int32 id, uint8 vol);
_vm->_sound->setFxIdVolume(1 + params[0], params[1]);
_vm->_sound->setFxIdVolume(params[0] + 1, params[1]);
return IR_CONT;
}
int32 Logic::fnStopFx(int32 *params) {
// params: 0 position in queue
// This will stop looped & random fx instantly, and remove the fx
// from the queue. So although it doesn't stop spot fx, it will
// remove them from the queue if they haven't yet played
int32 i = params[0];
uint32 rv = _vm->_sound->stopFx(i + 1);
uint8 j = (uint8) params[0];
uint32 id;
uint32 rv;
if (rv)
debug(5, "SFX ERROR: closeFx() returned %.8x", rv);
if (_vm->_fxQueue[j].type == FX_RANDOM || _vm->_fxQueue[j].type == FX_LOOP) {
id = (uint32) j + 1; // because 0 is not a valid id
// stop fx & remove sample from sound memory
rv = _vm->_sound->closeFx(id);
if (rv)
debug(5, "SFX ERROR: closeFx() returned %.8x", rv);
// Remove from queue
if (fxQueue[i].resource) {
_vm->_resman->closeResource(fxQueue[i].resource);
fxQueue[i].resource = 0;
}
// remove from queue
_vm->_fxQueue[j].resource = 0;
return IR_CONT;
}
int32 Logic::fnStopAllFx(int32 *params) {
// Stops all looped & random fx and clears the entire queue
/**
* Stops all FX and clears the entire FX queue.
*/
int32 Logic::fnStopAllFx(int32 *params) {
// params: none
_vm->clearFxQueue();

View File

@ -38,10 +38,14 @@ namespace Sword2 {
// fx types
enum {
// These three types correspond to types set by the scripts
FX_SPOT = 0,
FX_LOOP = 1,
FX_RANDOM = 2,
FX_SPOT2 = 3
// These are used for FX queue bookkeeping
FX_SPOT2 = 3,
FX_LOOPING = 4
};
} // End of namespace Sword2

View File

@ -321,16 +321,6 @@ public:
void setScrolling(void);
struct FxQueueEntry {
uint32 resource; // resource id of sample
uint16 delay; // cycles to wait before playing (or 'random chance' if FX_RANDOM)
uint8 volume; // 0..16
int8 pan; // -16..16
uint8 type; // FX_SPOT, FX_RANDOM or FX_LOOP
};
FxQueueEntry _fxQueue[FXQ_LENGTH];
// used to store id of tunes that loop, for save & restore
uint32 _loopingMusicId;