diff options
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | sword2/driver/animation.cpp | 8 | ||||
-rw-r--r-- | sword2/driver/d_sound.cpp | 144 | ||||
-rw-r--r-- | sword2/driver/d_sound.h | 7 | ||||
-rw-r--r-- | sword2/resman.cpp | 11 | ||||
-rw-r--r-- | sword2/sound.cpp | 245 | ||||
-rw-r--r-- | sword2/sound.h | 6 | ||||
-rw-r--r-- | sword2/sword2.h | 10 |
8 files changed, 173 insertions, 261 deletions
@@ -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 diff --git a/sword2/driver/animation.cpp b/sword2/driver/animation.cpp index 53dad119f5..b7d177ed84 100644 --- a/sword2/driver/animation.cpp +++ b/sword2/driver/animation.cpp @@ -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; } diff --git a/sword2/driver/d_sound.cpp b/sword2/driver/d_sound.cpp index e6b547fdae..b5cf40d9c2 100644 --- a/sword2/driver/d_sound.cpp +++ b/sword2/driver/d_sound.cpp @@ -1029,77 +1029,13 @@ bool Sound::isFxPlaying(int32 id) { } /** - * 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 * you will run out of sound effect buffers. * @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]; - } + if (type == RDSE_FXLEADIN || type == RDSE_FXLEADOUT) { + id = (type == RDSE_FXLEADIN) ? -2 : -1; + volume = _musicMuted ? 0 : _musicVolTable[_musicVol]; + } + + WavInfo wavInfo; - hr = openFx(id, data); - if (hr != RD_OK) - return hr; + if (!getWavInfo(data, &wavInfo)) { + warning("playFx: Not a valid WAV file"); + return RDERR_INVALIDWAV; } - i = getFxIndex(id); + int32 fxi = getFxIndex(id); - 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 (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; + + int8 p = _panTable[pan + 16]; - _vm->_mixer->playRaw(&_fx[i]._handle, _fx[i]._buf, _fx[i]._bufSize, _fx[i]._rate, _fx[i]._flags, -1, volume, p); + _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; } } diff --git a/sword2/driver/d_sound.h b/sword2/driver/d_sound.h index 19a3458510..ef10dd79e4 100644 --- a/sword2/driver/d_sound.h +++ b/sword2/driver/d_sound.h @@ -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); }; diff --git a/sword2/resman.cpp b/sword2/resman.cpp index 245f9819dd..c9c424b482 100644 --- a/sword2/resman.cpp +++ b/sword2/resman.cpp @@ -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) diff --git a/sword2/sound.cpp b/sword2/sound.cpp index 7d8c4b157e..28c4218d78 100644 --- a/sword2/sound.cpp +++ b/sword2/sound.cpp @@ -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; +} + +/** + * 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 + * 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) - return IR_CONT; + // Find a free slot in the FX queue - _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]; + for (i = 0; i < FXQ_LENGTH; i++) { + if (!fxQueue[i].resource) + break; } - _vm->_fxQueue[j].volume = params[3]; // 0..16 - _vm->_fxQueue[j].pan = params[4]; // -16..16 - - 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); + if (i == FXQ_LENGTH) { + warning("No free slot in FX queue"); + return IR_CONT; + } -#ifdef _SWORD2_DEBUG - header = (StandardHeader *) data; - if (header->fileType != WAV_FILE) - error("fnPlayFx given invalid resource"); -#endif + fxQueue[i].resource = params[0]; + fxQueue[i].type = params[1]; + fxQueue[i].delay = params[2]; - data += sizeof(StandardHeader); + 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; + } - // copy it to sound memory, using position in queue as 'id' - rv = _vm->_sound->openFx(id, data); + fxQueue[i].volume = params[3]; + fxQueue[i].pan = params[4]; - if (rv) - debug(5, "SFX ERROR: openFx() returned %.8x", rv); + byte *data = _vm->_resman->openResource(params[0]); + StandardHeader *header = (StandardHeader *) data; - // release the sample - _vm->_resman->closeResource(_vm->_fxQueue[j].resource); - } + assert(header->fileType == WAV_FILE); - 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].data = data + sizeof(StandardHeader); - // in case we want to call fnStopFx() later, to kill this fx - // (mainly for FX_LOOP & FX_RANDOM) + // 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] = j; + _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 - - uint8 j = (uint8) params[0]; - uint32 id; - uint32 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 + int32 i = params[0]; + uint32 rv = _vm->_sound->stopFx(i + 1); - // stop fx & remove sample from sound memory - rv = _vm->_sound->closeFx(id); + if (rv) + debug(5, "SFX ERROR: closeFx() returned %.8x", rv); - 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(); diff --git a/sword2/sound.h b/sword2/sound.h index 13ece67851..6c7baabe24 100644 --- a/sword2/sound.h +++ b/sword2/sound.h @@ -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 diff --git a/sword2/sword2.h b/sword2/sword2.h index 2d621622e7..588dd72106 100644 --- a/sword2/sword2.h +++ b/sword2/sword2.h @@ -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; |