Initial commit
This commit is contained in:
504
engines/agos/sfxparser_accolade.cpp
Normal file
504
engines/agos/sfxparser_accolade.cpp
Normal file
@@ -0,0 +1,504 @@
|
||||
/* 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 "agos/sfxparser_accolade.h"
|
||||
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace AGOS {
|
||||
|
||||
SfxParser_Accolade::SfxSlot::SfxSlot() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::SfxSlot::reset() {
|
||||
noteFractionDelta = 0;
|
||||
vibratoTime = 0;
|
||||
vibratoCounter = 0;
|
||||
vibratoDelta = 0;
|
||||
waitCounter = 0;
|
||||
loopStart = 0;
|
||||
loopCounter = 0;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::SfxSlot::clear() {
|
||||
allocated = false;
|
||||
active = false;
|
||||
source = -1;
|
||||
scriptPos = 0;
|
||||
playTime = 0;
|
||||
lastEventTime = 0;
|
||||
lastPlayedNote = -1;
|
||||
currentNoteFraction = 0;
|
||||
programChanged = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
bool SfxParser_Accolade::SfxSlot::atEndOfScript() {
|
||||
return scriptPos >= sfxData->scriptSize;
|
||||
}
|
||||
|
||||
int16 SfxParser_Accolade::SfxSlot::readScript(bool opCode) {
|
||||
if (atEndOfScript())
|
||||
error("SfxParser_Accolade::SfxData::readScript - attempt to read past the end of the script");
|
||||
|
||||
int16 data = sfxData->scriptData[scriptPos];
|
||||
scriptPos++;
|
||||
|
||||
if (opCode && (data <= 0 || data > 0xC)) {
|
||||
// Any opcode outside the range 1-B will cause the script to stop.
|
||||
data = 0xC;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const byte SfxParser_Accolade::INSTRUMENT_SIZE_MT32;
|
||||
const uint16 SfxParser_Accolade::SCRIPT_TIMER_FREQUENCY;
|
||||
const uint16 SfxParser_Accolade::SCRIPT_TIMER_RATE;
|
||||
|
||||
SfxParser_Accolade::SfxParser_Accolade() : _driver(nullptr), _timerRate(0), _sfxData(),
|
||||
_numSfx(0), _sourceAllocations { -1, -1, -1, -1 }, _paused(false) { }
|
||||
|
||||
SfxParser_Accolade::~SfxParser_Accolade() {
|
||||
stopAll();
|
||||
|
||||
if (_sfxData) {
|
||||
delete[] _sfxData;
|
||||
_sfxData = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::load(Common::SeekableReadStream *in, int32 size) {
|
||||
// First word is the total data size.
|
||||
uint16 dataSize = in->readUint16LE();
|
||||
if (dataSize > size)
|
||||
error("SfxParser_Accolade::load - Sound effect bank lists size %d but has file size %d", dataSize, size);
|
||||
|
||||
// Next word is the number of SFX definitions.
|
||||
_numSfx = in->readUint16LE();
|
||||
_sfxData = new SfxData[_numSfx];
|
||||
|
||||
// Next is a list of start offsets for each SFX definition. Combined with
|
||||
// the total size the size of each SFX definition can be determined.
|
||||
int64 indexStartPos = in->pos();
|
||||
for (int i = 0; i < _numSfx; i++) {
|
||||
in->seek(indexStartPos + (i * 2));
|
||||
uint16 sfxDataOffset = in->readUint16LE();
|
||||
uint16 sfxDataEndOffset = i < _numSfx - 1 ? in->readUint16LE() : dataSize - 4;
|
||||
in->seek(indexStartPos + sfxDataOffset);
|
||||
uint16 sfxDataSize = sfxDataEndOffset - sfxDataOffset;
|
||||
|
||||
// Read instrument definition.
|
||||
readInstrument(&_sfxData[i], in);
|
||||
|
||||
// Instrument data size is fixed; the reset of the SFX definition is
|
||||
// the script.
|
||||
int scriptSize = sfxDataSize - INSTRUMENT_SIZE_MT32 - INSTRUMENT_SIZE_ADLIB;
|
||||
if (scriptSize < 2)
|
||||
error("SfxParser_Accolade::load - Unexpected script size %d", scriptSize);
|
||||
if (scriptSize % 2 != 0)
|
||||
warning("SfxParser_Accolade::load - Script has odd number of bytes %d", scriptSize);
|
||||
scriptSize >>= 1;
|
||||
// Script size is stored in words.
|
||||
_sfxData[i].scriptSize = scriptSize;
|
||||
|
||||
// Read each word into the script data.
|
||||
for (int j = 0; j < scriptSize; j++) {
|
||||
_sfxData[i].scriptData[j] = in->readSint16LE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::setTimerRate(uint32 rate) {
|
||||
_timerRate = rate;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::play(uint8 sfxNumber) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (sfxNumber >= _numSfx) {
|
||||
warning("SfxParser_Accolade::play - Sound effect %d requested but bank has only %d sound effects", sfxNumber, _numSfx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find an unallocated slot.
|
||||
SfxSlot *sfxSlot = nullptr;
|
||||
int sfxSlotNum = -1;
|
||||
for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
|
||||
if (!_sfxSlots[i].allocated) {
|
||||
_sfxSlots[i].allocated = true;
|
||||
sfxSlot = &_sfxSlots[i];
|
||||
sfxSlotNum = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that the original interpreter would only output MIDI data from 2
|
||||
// slots simultaneously, but potentially *all* SFX could be active at the
|
||||
// same time (but the same sound effect could not play more than once at
|
||||
// the same time).
|
||||
// This implementation only allows 4 SFX active at the same time, which
|
||||
// seems to be more than enough for Elvira 2 and Waxworks.
|
||||
if (!sfxSlot)
|
||||
return;
|
||||
|
||||
// Allocate a source.
|
||||
for (int i = 0; i < getNumberOfSfxSources(); i++) {
|
||||
if (_sourceAllocations[i] == -1) {
|
||||
_sourceAllocations[i] = sfxSlotNum;
|
||||
sfxSlot->source = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the SFX data and load the instrument into the allocated channel.
|
||||
sfxSlot->sfxData = &_sfxData[sfxNumber];
|
||||
sfxSlot->programChanged = loadInstrument(sfxSlot);
|
||||
|
||||
// Activate the slot to start script execution.
|
||||
sfxSlot->active = true;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::stopAll() {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
|
||||
if (_sfxSlots[i].active)
|
||||
stop(&_sfxSlots[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::pauseAll(bool paused) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (_paused == paused)
|
||||
return;
|
||||
|
||||
_paused = paused;
|
||||
|
||||
if (_paused) {
|
||||
// Stop the current note for all active SFX.
|
||||
for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
|
||||
if (_sfxSlots[i].active)
|
||||
noteOff(&_sfxSlots[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::stop(SfxSlot *sfxSlot) {
|
||||
noteOff(sfxSlot);
|
||||
|
||||
// Deallocate the source.
|
||||
if (sfxSlot->source >= 0) {
|
||||
_driver->deinitSource(sfxSlot->source);
|
||||
_sourceAllocations[sfxSlot->source - 1] = -1;
|
||||
}
|
||||
|
||||
// The original interpreter would try to re-assign the source to an active
|
||||
// sound effect without a source. This is not implemented here because
|
||||
// Elvira 2 and Waxworks use SFX very sparingly, so it seems very unlikely
|
||||
// that more than 2 SFX would be active at the same time.
|
||||
|
||||
sfxSlot->clear();
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::processOpCode(SfxSlot *sfxSlot, byte opCode) {
|
||||
switch (opCode) {
|
||||
case 0x1:
|
||||
// Set note and note fraction delta.
|
||||
sfxSlot->noteFractionDelta = sfxSlot->readScript(false);
|
||||
break;
|
||||
case 0x2:
|
||||
// Clear note and note fraction delta.
|
||||
sfxSlot->noteFractionDelta = 0;
|
||||
break;
|
||||
case 0x3:
|
||||
// Set vibrato.
|
||||
int16 vibratoTime;
|
||||
vibratoTime = sfxSlot->readScript(false);
|
||||
assert(vibratoTime >= 0);
|
||||
sfxSlot->vibratoTime = vibratoTime;
|
||||
// The counter starts at half the vibrato time, which causes the note
|
||||
// frequency to move above and below the note fraction (like a sine
|
||||
// wave).
|
||||
sfxSlot->vibratoCounter = (vibratoTime >> 1) | 1;
|
||||
sfxSlot->vibratoDelta = sfxSlot->readScript(false);
|
||||
break;
|
||||
case 0x4:
|
||||
// Clear vibrato.
|
||||
sfxSlot->vibratoTime = 0;
|
||||
sfxSlot->vibratoDelta = 0;
|
||||
break;
|
||||
case 0x5:
|
||||
// Wait.
|
||||
sfxSlot->waitCounter = sfxSlot->readScript(false);
|
||||
assert(sfxSlot->waitCounter >= 0);
|
||||
break;
|
||||
case 0x6:
|
||||
// Play note.
|
||||
noteOff(sfxSlot);
|
||||
int8 note;
|
||||
note = sfxSlot->readScript(false) & 0xFF;
|
||||
assert(note >= 0);
|
||||
sfxSlot->currentNoteFraction = note << 8;
|
||||
noteOn(sfxSlot);
|
||||
break;
|
||||
case 0x7:
|
||||
// Loop start.
|
||||
|
||||
// Just register the loop start position.
|
||||
sfxSlot->loopStart = sfxSlot->scriptPos;
|
||||
break;
|
||||
case 0x8:
|
||||
// Loop next.
|
||||
int16 loopParam;
|
||||
loopParam = sfxSlot->readScript(false);
|
||||
assert(loopParam >= 0);
|
||||
if (sfxSlot->loopCounter == 0) {
|
||||
// Loop counter has not been set yet, so do this now.
|
||||
if (loopParam == 0)
|
||||
// Loop infinitely.
|
||||
loopParam = -1;
|
||||
sfxSlot->loopCounter = loopParam;
|
||||
// Go back to loop start.
|
||||
sfxSlot->scriptPos = sfxSlot->loopStart;
|
||||
} else {
|
||||
// Decrease loop counter, unless the loop is infinite.
|
||||
if (sfxSlot->loopCounter != -1)
|
||||
sfxSlot->loopCounter--;
|
||||
if (sfxSlot->loopCounter != 0)
|
||||
// Go back to loop start.
|
||||
sfxSlot->scriptPos = sfxSlot->loopStart;
|
||||
// Else continue the script.
|
||||
}
|
||||
break;
|
||||
case 0x9:
|
||||
// Stop the current note.
|
||||
noteOff(sfxSlot);
|
||||
break;
|
||||
case 0xA:
|
||||
// Reset sound effect data.
|
||||
sfxSlot->reset();
|
||||
// The original interpreter does this; not sure why...
|
||||
sfxSlot->vibratoCounter = 1;
|
||||
break;
|
||||
case 0xB:
|
||||
// Noop. Consumes 1 script parameter.
|
||||
sfxSlot->scriptPos++;
|
||||
break;
|
||||
case 0xC:
|
||||
default:
|
||||
// Stop the sound effect.
|
||||
stop(sfxSlot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::noteOn(SfxSlot *sfxSlot) {
|
||||
byte note = sfxSlot->currentNoteFraction >> 8;
|
||||
if (sfxSlot->source >= 0)
|
||||
// Send a note on with maximum velocity.
|
||||
_driver->send(sfxSlot->source, 0x90 | (note << 8) | (0x7F << 16));
|
||||
sfxSlot->lastPlayedNote = note;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::noteOff(SfxSlot *sfxSlot) {
|
||||
if (sfxSlot->lastPlayedNote < 0)
|
||||
return;
|
||||
|
||||
if (sfxSlot->source >= 0)
|
||||
// Send a note off.
|
||||
_driver->send(sfxSlot->source, 0x80 | (sfxSlot->lastPlayedNote << 8));
|
||||
sfxSlot->lastPlayedNote = -1;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::onTimer() {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (_paused)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
|
||||
if (!_sfxSlots[i].active)
|
||||
continue;
|
||||
|
||||
if (!_sfxSlots[i].programChanged) {
|
||||
// If the SFX instrument has not yet been set on the allocated
|
||||
// channel, wait until the driver is ready.
|
||||
if (!_driver->isReady(_sfxSlots[i].source))
|
||||
continue;
|
||||
|
||||
// Then change to the SFX instrument.
|
||||
changeInstrument(&_sfxSlots[i]);
|
||||
_sfxSlots[i].programChanged = true;
|
||||
}
|
||||
|
||||
// Determine the end time of the timer callback period that will be
|
||||
// processed.
|
||||
uint32 endTime = _sfxSlots[i].playTime + _timerRate;
|
||||
// Process script ticks while the sound effect remains active.
|
||||
while (_sfxSlots[i].active) {
|
||||
// Determine the end time of the script tick that will be
|
||||
// processed.
|
||||
uint32 eventTime = _sfxSlots[i].lastEventTime + SCRIPT_TIMER_RATE;
|
||||
if (eventTime > endTime)
|
||||
// Script tick end time is after this timer callback period, so
|
||||
// leave it to the next callback invocation.
|
||||
break;
|
||||
|
||||
// Process this script tick.
|
||||
_sfxSlots[i].lastEventTime = eventTime;
|
||||
|
||||
// Process vibrato counter.
|
||||
if (_sfxSlots[i].vibratoCounter > 0) {
|
||||
// Count down the vibrato counter.
|
||||
_sfxSlots[i].vibratoCounter--;
|
||||
} else {
|
||||
// Vibrato counter has reached zero. The vibrato note fraction
|
||||
// delta is negated, so that it will now be subtracted from the
|
||||
// note fraction instead of added, or the other way around.
|
||||
_sfxSlots[i].vibratoDelta = -_sfxSlots[i].vibratoDelta;
|
||||
// Reset the vibrato counter so it counts down to the next
|
||||
// delta negation.
|
||||
_sfxSlots[i].vibratoCounter = _sfxSlots[i].vibratoTime;
|
||||
}
|
||||
|
||||
// Calculate the new note and note fraction by adding the deltas.
|
||||
uint16 newNoteFraction = _sfxSlots[i].currentNoteFraction;
|
||||
newNoteFraction += _sfxSlots[i].noteFractionDelta;
|
||||
newNoteFraction += _sfxSlots[i].vibratoDelta;
|
||||
|
||||
if (newNoteFraction != _sfxSlots[i].currentNoteFraction) {
|
||||
// Update the note fraction.
|
||||
_sfxSlots[i].currentNoteFraction = newNoteFraction;
|
||||
updateNote(&_sfxSlots[i]);
|
||||
}
|
||||
|
||||
// Process the script.
|
||||
if (_sfxSlots[i].waitCounter > 0) {
|
||||
// Count down the wait counter. No script opcode will be
|
||||
// processsed.
|
||||
_sfxSlots[i].waitCounter--;
|
||||
} else if (_sfxSlots[i].atEndOfScript()) {
|
||||
// The end of the script has been reached, so stop the sound
|
||||
// effect.
|
||||
// Note that the original interpreter did not do any bounds
|
||||
// checking. Some scripts are not terminated properly and would
|
||||
// cause the original interpreter to overread into the
|
||||
// instrument data of the next sound effect.
|
||||
stop(&_sfxSlots[i]);
|
||||
} else {
|
||||
// Process the next script opcode.
|
||||
byte opCode = _sfxSlots[i].readScript(true) & 0xFF;
|
||||
processOpCode(&_sfxSlots[i], opCode);
|
||||
}
|
||||
}
|
||||
|
||||
// If the sound effect is still active, update the play timestamp.
|
||||
if (_sfxSlots[i].active)
|
||||
_sfxSlots[i].playTime = endTime;
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::timerCallback(void *data) {
|
||||
((SfxParser_Accolade *)data)->onTimer();
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_AdLib::setMidiDriver(MidiDriver_Multisource *driver) {
|
||||
_driver = driver;
|
||||
_adLibDriver = dynamic_cast<MidiDriver_Accolade_AdLib *>(driver);
|
||||
assert(_adLibDriver);
|
||||
}
|
||||
|
||||
byte SfxParser_Accolade_AdLib::getNumberOfSfxSources() {
|
||||
// The number of available sources depends on the OPL chip emulation used.
|
||||
return _adLibDriver->getNumberOfSfxSources();
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_AdLib::readInstrument(SfxData *sfxData, Common::SeekableReadStream *in) {
|
||||
in->skip(INSTRUMENT_SIZE_MT32);
|
||||
in->read(sfxData->instrumentDefinition, INSTRUMENT_SIZE_ADLIB);
|
||||
}
|
||||
|
||||
bool SfxParser_Accolade_AdLib::loadInstrument(SfxSlot *sfxSlot) {
|
||||
if (sfxSlot->source < 0)
|
||||
return true;
|
||||
|
||||
_adLibDriver->loadSfxInstrument(sfxSlot->source, sfxSlot->sfxData->instrumentDefinition);
|
||||
// No separate instrument change is necessary for AdLib, so true is
|
||||
// returned to indicate the instrument is already changed.
|
||||
return true;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_AdLib::noteOn(SfxSlot *sfxSlot) {
|
||||
if (sfxSlot->source >= 0)
|
||||
// Set the current note fraction first.
|
||||
_adLibDriver->setSfxNoteFraction(sfxSlot->source, sfxSlot->currentNoteFraction);
|
||||
// Then start the note.
|
||||
SfxParser_Accolade::noteOn(sfxSlot);
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_AdLib::updateNote(SfxSlot *sfxSlot) {
|
||||
if (sfxSlot->source < 0)
|
||||
return;
|
||||
|
||||
// Set the current note fraction first.
|
||||
_adLibDriver->setSfxNoteFraction(sfxSlot->source, sfxSlot->currentNoteFraction);
|
||||
// Then update the note.
|
||||
_adLibDriver->updateSfxNote(sfxSlot->source);
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_MT32::setMidiDriver(MidiDriver_Multisource *driver) {
|
||||
_driver = driver;
|
||||
_mt32Driver = dynamic_cast<MidiDriver_Accolade_MT32 *>(driver);
|
||||
assert(_mt32Driver);
|
||||
}
|
||||
|
||||
byte SfxParser_Accolade_MT32::getNumberOfSfxSources() {
|
||||
// MT-32 always uses 2 SFX sources.
|
||||
return 2;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_MT32::readInstrument(SfxData *sfxSlot, Common::SeekableReadStream *in) {
|
||||
in->read(sfxSlot->instrumentDefinition, INSTRUMENT_SIZE_MT32);
|
||||
in->skip(INSTRUMENT_SIZE_ADLIB);
|
||||
}
|
||||
|
||||
bool SfxParser_Accolade_MT32::loadInstrument(SfxSlot *sfxSlot) {
|
||||
if (sfxSlot->source < 0)
|
||||
return true;
|
||||
|
||||
_mt32Driver->loadSfxInstrument(sfxSlot->source, sfxSlot->sfxData->instrumentDefinition);
|
||||
// MT-32 requires a program change after the SysExes to load the instrument
|
||||
// have been processed. Return false to indicate this.
|
||||
return false;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_MT32::changeInstrument(SfxSlot *sfxData) {
|
||||
if (sfxData->source < 0)
|
||||
return;
|
||||
|
||||
_mt32Driver->changeSfxInstrument(sfxData->source);
|
||||
}
|
||||
|
||||
} // End of namespace AGOS
|
||||
Reference in New Issue
Block a user