diff options
author | Paul Gilbert | 2014-02-19 23:21:05 -0500 |
---|---|---|
committer | Paul Gilbert | 2014-02-19 23:21:05 -0500 |
commit | de384bae2ef1b904224b0fe00326e5a0597aafb6 (patch) | |
tree | bcc1f56dfb9c729b5de0812fcb5e4412007e09b8 /engines/mads/nebular | |
parent | 58bb1383d0b11d357128ca2b0a7634f091c0fb5b (diff) | |
download | scummvm-rg350-de384bae2ef1b904224b0fe00326e5a0597aafb6.tar.gz scummvm-rg350-de384bae2ef1b904224b0fe00326e5a0597aafb6.tar.bz2 scummvm-rg350-de384bae2ef1b904224b0fe00326e5a0597aafb6.zip |
MADS: Moved sound_nebular file into new nebular/ folder
Diffstat (limited to 'engines/mads/nebular')
-rw-r--r-- | engines/mads/nebular/sound_nebular.cpp | 1180 | ||||
-rw-r--r-- | engines/mads/nebular/sound_nebular.h | 408 |
2 files changed, 1588 insertions, 0 deletions
diff --git a/engines/mads/nebular/sound_nebular.cpp b/engines/mads/nebular/sound_nebular.cpp new file mode 100644 index 0000000000..9c3ac61f2d --- /dev/null +++ b/engines/mads/nebular/sound_nebular.cpp @@ -0,0 +1,1180 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" +#include "common/algorithm.h" +#include "common/debug.h" +#include "common/memstream.h" +#include "mads/sound.h" +#include "mads/nebular/sound_nebular.h" + +namespace MADS { + +namespace Nebular { + +AdlibChannel::AdlibChannel() { + _activeCount = 0; + _field1 = 0; + _field2 = 0; + _field3 = 0; + _field4 = 0; + _sampleIndex = 0; + _volume = 0; + _field7 = 0; + _field8 = 0; + _field9 = 0; + _fieldA = 0; + _fieldB = 0; + _fieldC = 0; + _fieldD = 0; + _fieldE = 0; + _ptr1 = nullptr; + _pSrc = nullptr; + _ptr3 = nullptr; + _ptr4 = nullptr; + _field17 = 0; + _field19 = 0; + _soundData = nullptr; + _field1D = 0; + _field1E = 0; + _field1F = 0; +} + +void AdlibChannel::reset() { + _activeCount = 0; + _field1 = 0; + _field2 = 0; + _field3 = 0; +} + +void AdlibChannel::enable(int flag) { + if (_activeCount) { + _fieldE = flag; + + // WORKAROUND: Original set _soundData pointer to flag. Since this seems + // just intended to invalidate any prior pointer, I've replaced it with + // a simple null pointer + _soundData = nullptr; + } +} + +void AdlibChannel::setPtr2(byte *pData) { + _pSrc = pData; + _field2 = 0xFF; + _fieldA = 1; + _field9 = 1; +} + +void AdlibChannel::load(byte *pData) { + _ptr1 = _pSrc = _ptr3 = pData; + _ptr4 = _soundData = pData; + _fieldA = 0xFF; + _activeCount = 1; + _fieldD = 64; + _field1 = 0; + _field1F = 0; + _field2 = _field3 = 0; + _volume = _field7 = 0; + _field1D = _field1E = 0; + _fieldE = 0; + _field9 = 0; + _fieldB = 0; + _field17 = 0; + _field19 = 0; +} + +void AdlibChannel::check(byte *nullPtr) { + if (_activeCount && _fieldE) { + if (!_field1E) { + _pSrc = nullPtr; + _fieldE = 0; + } else { + _field2 = 0xFF; + _fieldA = 4; + if (!_field9) + _field9 = 1; + } + } +} + +/*-----------------------------------------------------------------------*/ + +AdlibSample::AdlibSample(Common::SeekableReadStream &s) { + _attackRate = s.readByte(); + _decayRate = s.readByte(); + _sustainLevel = s.readByte(); + _releaseRate = s.readByte(); + _egTyp = s.readByte() != 0; + _ksr = s.readByte() != 0; + _totalLevel = s.readByte(); + _scalingLevel = s.readByte(); + _waveformSelect = s.readByte(); + _freqMultiple = s.readByte(); + _feedback = s.readByte(); + _ampMod = s.readByte() != 0; + _vib = s.readByte(); + _alg = s.readByte(); + _fieldE = s.readByte(); + s.skip(1); + _freqMask = s.readUint16LE(); + _freqBase = s.readUint16LE(); + _field14 = s.readUint16LE(); +} + +/*-----------------------------------------------------------------------*/ + +ASound::ASound(Audio::Mixer *mixer, const Common::String &filename, int dataOffset) { + // Open up the appropriate sound file + if (!_soundFile.open(filename)) + error("Could not open file - %s", filename.c_str()); + + // Initialise fields + _activeChannelPtr = nullptr; + _samplePtr = nullptr; + _frameCounter = 0; + _isDisabled = false; + _v1 = 0; + _v2 = 0; + _activeChannelNumber = 0; + _freqMask1 = _freqMask2 = 0; + _freqBase1 = _freqBase2 = 0; + _channelNum1 = _channelNum2 = 0; + _v7 = 0; + _v8 = 0; + _v9 = 0; + _v10 = 0; + _pollResult = 0; + _resultFlag = 0; + _nullData[0] = _nullData[1] = 0; + Common::fill(&_ports[0], &_ports[256], 0); + _stateFlag = false; + _activeChannelReg = 0; + _v11 = 0; + _randomSeed = 1234; + _amDep = _vibDep = _splitPoint = true; + + _samplesTillCallback = 0; + _samplesTillCallbackRemainder = 0; + _samplesPerCallback = getRate() / CALLBACKS_PER_SECOND; + _samplesPerCallbackRemainder = getRate() % CALLBACKS_PER_SECOND; + + // Store passed parameters, and setup OPL + _dataOffset = dataOffset; + _mixer = mixer; + _opl = OPL::Config::create(); + assert(_opl); + + _opl->init(getRate()); + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, + Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + + // Initialise the Adlib + adlibInit(); + + // Reset the adlib + command0(); +} + +ASound::~ASound() { + Common::List<CachedDataEntry>::iterator i; + for (i = _dataCache.begin(); i != _dataCache.end(); ++i) + delete[] (*i)._data; + + _mixer->stopHandle(_soundHandle); + delete _opl; +} + +void ASound::adlibInit() { + write(4, 0x60); + write(4, 0x80); + write(2, 0xff); + write(4, 0x21); + write(4, 0x60); + write(4, 0x80); +} + +int ASound::stop() { + command0(); + int result = _pollResult; + _pollResult = 0; + return result; +} + +int ASound::poll() { + // Update any playing sounds + update(); + + // Return result + int result = _pollResult; + _pollResult = 0; + return result; +} + +void ASound::noise() { + int randomVal = getRandomNumber(); + + if (_v1) { + setFrequency(_channelNum1, ((randomVal ^ 0xFFFF) & _freqMask1) + _freqBase1); + } + + if (_v2) { + setFrequency(_channelNum2, (randomVal & _freqMask2) + _freqBase2); + } +} + +void ASound::write(int reg, int val) { + _queue.push(RegisterValue(reg, val)); +} + +int ASound::write2(int state, int reg, int val) { + // TODO: Original has a state parameter, not used when in Adlib mode? + _ports[reg] = val; + write(reg, val); + return state; +} + +void ASound::flush() { + Common::StackLock slock(_driverMutex); + + while (!_queue.empty()) { + RegisterValue v = _queue.pop(); + _opl->writeReg(v._regNum, v._value); + } +} + +void ASound::channelOn(int reg, int volume) { + write2(8, reg, (_ports[reg] & 0xC0) | (volume & 0x3F)); +} + +void ASound::channelOff(int reg) { + write2(8, reg, _ports[reg] | 0x3F); +} + +void ASound::resultCheck() { + if (_resultFlag != 1) { + _resultFlag = 1; + _pollResult = 1; + } +} + +byte *ASound::loadData(int offset, int size) { + // First scan for an existing copy + Common::List<CachedDataEntry>::iterator i; + for (i = _dataCache.begin(); i != _dataCache.end(); ++i) { + CachedDataEntry &e = *i; + if (e._offset == offset) + return e._data; + } + + // No existing entry found, so load up data and store as a new entry + CachedDataEntry rec; + rec._offset = offset; + rec._data = new byte[size]; + _soundFile.seek(_dataOffset + offset); + _soundFile.read(rec._data, size); + _dataCache.push_back(rec); + + // Return the data + return rec._data; +} + +void ASound::playSound(int offset, int size) { + // Load the specified data block + playSound(loadData(offset, size)); +} + +void ASound::playSound(byte *pData) { + // Scan for a high level free channel + for (int i = 5; i < ADLIB_CHANNEL_COUNT; ++i) { + if (!_channels[i]._activeCount) { + _channels[i].load(pData); + return; + } + } + + // None found, do a secondary scan for an interruptable channel + for (int i = ADLIB_CHANNEL_COUNT - 1; i >= ADLIB_CHANNEL_MIDWAY; --i) { + if (_channels[i]._fieldE == 0xFF) { + _channels[i].load(pData); + return; + } + } +} + +bool ASound::isSoundActive(byte *pData) { + for (int i = 0; i < ADLIB_CHANNEL_MIDWAY; ++i) { + if (_channels[i]._activeCount && _channels[i]._soundData == pData) + return true; + } + + return false; +} + +void ASound::setFrequency(int channel, int freq) { + write2(8, 0xA0 + channel, freq & 0xFF); + write2(8, 0xB0 + channel, (freq >> 8) | 0x20); +} + +int ASound::getRandomNumber() { + int v = 0x9248 + (int)_randomSeed; + _randomSeed = ((v >> 3) | (v << 13)) & 0xFFFF; + return _randomSeed; +} + +void ASound::update() { + getRandomNumber(); + if (_isDisabled) + return; + + ++_frameCounter; + pollChannels(); + checkChannels(); + + if (_v1 == _v2) { + if (_resultFlag != -1) { + _resultFlag = -1; + _pollResult = -1; + } + } else { + if (_v1) { + _freqBase1 += _v7; + if (!--_v1) { + if (!_v2 || _channelNum1 != _channelNum2) { + write2(8, 0xA0 + _channelNum1, 0); + write2(8, 0xB0 + _channelNum1, 0); + } + } + } + + if (_v2) { + _freqBase2 += _v8; + if (!--_v2) { + if (!_v1 || _channelNum2 != _channelNum1) { + write2(8, 0xA0 + _channelNum2, 0); + write2(8, 0xB0 + _channelNum2, 0); + } + } + } + } +} + +void ASound::pollChannels() { + _activeChannelNumber = 0; + for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) { + _activeChannelPtr = &_channels[i]; + pollActiveChannel(); + } +} + +void ASound::checkChannels() { + for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) + _channels[i].check(_nullData); +} + +void ASound::pollActiveChannel() { + AdlibChannel *chan = _activeChannelPtr; + bool updateFlag = true; + + if (chan->_activeCount) { + if (chan->_field8 > 0 && --chan->_field8 == 0) + updateOctave(); + + if (--_activeChannelPtr->_activeCount <= 0) { + for (;;) { + byte *pSrc = chan->_pSrc; + if (!(*pSrc & 0x80) || (*pSrc <= 0xF0)) { + if (updateFlag) + updateActiveChannel(); + + chan->_field4 = *pSrc++; + chan->_activeCount = *pSrc++; + chan->_pSrc += 2; + + if (!chan->_field4 || !chan->_activeCount) { + updateOctave(); + } else { + chan->_field8 = chan->_activeCount - chan->_field7; + updateChannelState(); + } + + // Break out of processing loop + break; + } else { + updateFlag = false; + + switch ((~*pSrc) & 0xF) { + case 0: + if (!chan->_field17) { + if (*++pSrc == 0) { + chan->_pSrc += 2; + chan->_ptr3 = chan->_pSrc; + chan->_field17 = 0; + } else { + chan->_field17 = *pSrc; + chan->_pSrc = chan->_ptr3; + } + } else if (--chan->_field17) { + chan->_pSrc = chan->_ptr3; + } else { + chan->_pSrc += 2; + chan->_ptr3 = chan->_pSrc; + } + break; + + case 1: + if (!chan->_field19) { + if (*++pSrc == 0) { + chan->_pSrc += 2; + chan->_ptr4 = chan->_pSrc; + chan->_ptr3 = chan->_pSrc; + chan->_field17 = 0; + chan->_field19 = 0; + } else { + chan->_field19 = *pSrc; + chan->_pSrc = chan->_ptr4; + chan->_ptr3 = chan->_ptr4; + } + } else if (--chan->_field19) { + chan->_ptr4 = chan->_pSrc; + chan->_ptr3 = chan->_pSrc; + } else { + chan->_pSrc += 2; + chan->_ptr4 = chan->_pSrc; + chan->_ptr3 = chan->_pSrc; + } + break; + + case 2: + // Loop sound data + chan->_field1 = 0; + chan->_field2 = chan->_field3 = 0; + chan->_volume = chan->_field7 = 0; + chan->_field1D = chan->_field1E = 0; + chan->_field8 = 0; + chan->_field9 = 0; + chan->_fieldB = 0; + chan->_field17 = 0; + chan->_field19 = 0; + chan->_fieldD = 0x40; + chan->_ptr1 = chan->_soundData; + chan->_pSrc = chan->_soundData; + chan->_ptr3 = chan->_soundData; + chan->_ptr4 = chan->_soundData; + + chan->_pSrc += 2; + break; + + case 3: + chan->_sampleIndex = *++pSrc; + chan->_pSrc += 2; + loadSample(chan->_sampleIndex); + break; + + case 4: + chan->_field7 = *++pSrc; + chan->_pSrc += 2; + break; + + case 5: + chan->_field1 = *++pSrc; + chan->_pSrc += 2; + break; + + case 6: + ++pSrc; + if (chan->_fieldE) { + chan->_pSrc += 2; + } else { + chan->_volume = *pSrc >> 1; + updateFlag = true; + chan->_pSrc += 2; + } + break; + + case 7: + ++pSrc; + if (!chan->_fieldE) { + chan->_fieldA = *pSrc; + chan->_field2 = *++pSrc; + chan->_field9 = 1; + } + + chan->_pSrc += 3; + break; + + case 8: + chan->_field1D = *++pSrc; + chan->_pSrc += 2; + break; + + case 9: { + int v1 = *++pSrc; + ++pSrc; + int v2 = (v1 - 1) & getRandomNumber(); + int v3 = pSrc[v2]; + int v4 = pSrc[v1]; + + pSrc[v4 + v1 + 1] = v3; + chan->_pSrc += v1 + 3; + break; + } + + case 10: + ++pSrc; + if (chan->_fieldE) { + chan->_pSrc += 2; + } else { + chan->_field1E = *pSrc >> 1; + updateFlag = true; + chan->_pSrc += 2; + } + break; + + case 11: + chan->_fieldD = *++pSrc; + updateFlag = true; + chan->_pSrc += 2; + break; + + case 12: + chan->_fieldC = *++pSrc; + chan->_field3 = *++pSrc; + chan->_fieldB = 1; + chan->_pSrc += 2; + break; + + case 13: + ++pSrc; + chan->_pSrc += 2; + break; + + case 14: + chan->_field1F = *++pSrc; + chan->_pSrc += 2; + break; + + default: + break; + } + } + } + } + + if (chan->_field1) + updateFNumber(); + + updateFlag = false; + if (chan->_field9 || chan->_fieldB) { + if (!--chan->_field9) { + chan->_field9 = chan->_fieldA; + if (chan->_field2) { + int8 newVal = (int8)chan->_field2 + (int8)chan->_field1E; + if (newVal < 0) { + chan->_field9 = 0; + newVal = 0; + } else if (newVal > 63) { + chan->_field9 = 0; + newVal = 63; + } + + chan->_field1E = newVal; + updateFlag = true; + } + } + + if (!--chan->_fieldB) { + chan->_fieldB = chan->_fieldC; + if (chan->_field3) { + chan->_fieldD = chan->_field3; + updateFlag = true; + } + } + + if (updateFlag) + updateActiveChannel(); + } + } + + ++_activeChannelNumber; +} + +void ASound::updateOctave() { + int reg = 0xB0 + _activeChannelNumber; + write2(8, reg, _ports[reg] & 0xDF); +} + +static int _vList1[] = { + 0x200, 0x21E, 0x23F, 0x261, 0x285, 0x2AB, + 0x2D4, 0x2FF, 0x32D, 0x35D, 0x390, 0x3C7 +}; + +void ASound::updateChannelState() { + updateActiveChannel(); + + if (_channelData[_activeChannelNumber]._field0) { + if (_channelNum1 == _activeChannelNumber) + _stateFlag = 0; + if (_channelNum2 == _activeChannelNumber) + _stateFlag = 1; + + if (!_stateFlag) { + _stateFlag = 1; + if (_v1) + write2(8, 0xB0 + _channelNum1, _ports[0xB0 + _channelNum1] & 0xDF); + + _channelNum1 = _activeChannelNumber; + _v1 = _channelData[_channelNum1]._field0; + _freqMask1 = _channelData[_channelNum1]._freqMask; + _freqBase1 = _channelData[_channelNum1]._freqBase; + _v7 = _channelData[_channelNum1]._field6; + } else { + _stateFlag = 0; + if (_v2) + write2(8, 0xB0 + _channelNum2, _ports[0xB0 + _channelNum2] & 0xDF); + + _channelNum2 = _activeChannelNumber; + _v2 = _channelData[_channelNum2]._field0; + _freqMask2 = _channelData[_channelNum2]._freqMask; + _freqBase2 = _channelData[_channelNum2]._freqBase; + _v8 = _channelData[_channelNum2]._field6; + } + + resultCheck(); + } else { + int reg = 0xA0 + _activeChannelNumber; + int vTimes = (_activeChannelPtr->_field4 + _activeChannelPtr->_field1F) / 12; + int vOffset = (_activeChannelPtr->_field4 + _activeChannelPtr->_field1F) % 12; + int val = _vList1[vOffset] + _activeChannelPtr->_field1D; + write2(8, reg, val & 0xFF); + + reg += 0x10; + write2(8, reg, (_ports[reg] & 0x20) | (vTimes << 2) | (val >> 8)); + + write2(8, reg, _ports[reg] | 0x20); + } +} + +static const int outputIndexes[] = { + 3, 1, 4, 2, 5, 6, 9, 7, 10, 8, 11, 12, 15, 13, 16, 14, 17 +}; +static const int outputChannels[] = { + 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 0 +}; +static const int volumeList[] = { + 0x3F, 0x3F, 0x36, 0x31, 0x2D, 0x2A, 0x28, 0x26, 0x24, 0x22, 0x21, 0x20, 0x1F, 0x1E, 0x1D, 0x1C, + 0x1B, 0x1A, 0x19, 0x19, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14, 0x14, 0x13, 0x12, 0x12, + 0x11, 0x11, 0x10, 0x10, 0x0F, 0x0F, 0x0E, 0x0E, 0x0D, 0x0D, 0x0C, 0x0C, 0x0B, 0x0B, 0x0A, 0x0A, + 0x0A, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +void ASound::updateActiveChannel() { + int reg = 0x40 + outputChannels[outputIndexes[_activeChannelNumber * 2]]; + int portVal = _ports[reg] & 0xFFC0; + int newVolume = CLIP(_activeChannelPtr->_volume + _activeChannelPtr->_field1E, 0, 63); + + // Note: Original had a whole block not seeming to be used, since the initialisation + // sets a variable to 5660h, and doesn't change it, so the branch is never taken + int val = CLIP(newVolume - volumeList[_activeChannelPtr->_fieldD], 0, 63); + val = (63 - val) | portVal; + + int val2 = CLIP(newVolume - volumeList[-(_activeChannelPtr->_fieldD - 127)], 0, 63); + val2 = (63 - val2) | portVal; + write2(0, reg, val); + write2(2, reg, val2); +} + +void ASound::loadSample(int sampleIndex) { + _activeChannelReg = 0xB0 + _activeChannelNumber; + write2(8, _activeChannelReg, _ports[_activeChannelReg] & 0xDF); + + _activeChannelReg = _activeChannelNumber; + _samplePtr = &_samples[sampleIndex * 2]; + _v11 = outputChannels[outputIndexes[_activeChannelReg * 2 - 1]]; + processSample(); + + AdlibChannelData &cd = _channelData[_activeChannelNumber]; + cd._field6 = _samplePtr->_field14; + cd._freqBase = _samplePtr->_freqBase; + cd._freqMask = _samplePtr->_freqMask; + cd._field0 = _samplePtr->_fieldE; + + _samplePtr = &_samples[sampleIndex * 2 + 1]; + _v11 = outputChannels[outputIndexes[_activeChannelReg * 2]]; + processSample(); +} + +void ASound::processSample() { + // Write out vib flags and split point + write2(8, 0x40 + _v11, 0x3F); + int depthRhythm = (_ports[0xBD] & 0x3F) | (_amDep ? 0x80 : 0) | + (_vibDep ? 0x40 : 0); + write2(8, 0xBD, depthRhythm); + write2(8, 8, _splitPoint ? 0x40 : 0); + + // Write out feedback & Alg + int val = (_samplePtr->_feedback << 1) | (1 - _samplePtr->_alg); + write2(8, 0xC0 + _activeChannelReg, val); + + // Write out attack/decay rate + val = (_samplePtr->_attackRate << 4) | (_samplePtr->_decayRate & 0xF); + write2(8, 0x60 + _v11, val); + + // Write out sustain level/release rate + val = (_samplePtr->_sustainLevel << 4) | (_samplePtr->_releaseRate & 0xF); + write2(8, 0x80 + _v11, val); + + // Write out misc flags + val = (_samplePtr->_ampMod ? 0x80 : 0) | (_samplePtr->_vib ? 0x40 : 0) + | (_samplePtr->_egTyp ? 0x20 : 0) | (_samplePtr->_ksr ? 0x10 : 0) + | (_samplePtr->_freqMultiple & 0xF); + write2(8, 0x20 + _v11, val); + + // Write out waveform select + write2(8, 0xE0 + _v11, _samplePtr->_waveformSelect & 3); + + // Write out total level & scaling level + val = -((_samplePtr->_totalLevel & 0x3F) - 0x3F) | (_samplePtr->_scalingLevel << 6); + write2(8, 0x40 + _v11, val); +} + +void ASound::updateFNumber() { + int loReg = 0xA0 + _activeChannelNumber; + int hiReg = 0xB0 + _activeChannelNumber; + int val1 = (_ports[hiReg] & 0x1F) << 8; + val1 += _ports[loReg] + _activeChannelPtr->_field1; + write2(8, loReg, val1); + + int val2 = (_ports[hiReg] & 0x20) | (val1 >> 8); + write2(8, hiReg, val2); +} + +int ASound::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock slock(_driverMutex); + + int32 samplesLeft = numSamples; + memset(buffer, 0, sizeof(int16) * numSamples); + while (samplesLeft) { + if (!_samplesTillCallback) { + poll(); + flush(); + + _samplesTillCallback = _samplesPerCallback; + _samplesTillCallbackRemainder += _samplesPerCallbackRemainder; + if (_samplesTillCallbackRemainder >= CALLBACKS_PER_SECOND) { + _samplesTillCallback++; + _samplesTillCallbackRemainder -= CALLBACKS_PER_SECOND; + } + } + + int32 render = MIN<int>(samplesLeft, _samplesTillCallback); + samplesLeft -= render; + _samplesTillCallback -= render; + + _opl->readBuffer(buffer, render); + buffer += render; + } + return numSamples; +} + +int ASound::command0() { + bool isDisabled = _isDisabled; + _isDisabled = true; + + for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) + _channels[i].reset(); + + _v1 = 0; + _v2 = 0; + _freqMask1 = _freqMask2 = 0; + _freqBase1 = _freqBase2 = 0; + _v7 = 0; + _v8 = 0; + + // Reset Adlib port registers + for (int reg = 0x4F; reg >= 0x40; --reg) + write2(8, reg, 0x3F); + for (int reg = 0xFF; reg >= 0x60; --reg) + write2(8, reg, 0); + for (int reg = 0x3F; reg > 0; --reg) + write2(8, reg, 0); + write2(8, 1, 0x20); + + _isDisabled = isDisabled; + return 0; +} + +int ASound::command1() { + for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) + _channels[i].enable(0xFF); + return 0; +} + +int ASound::command2() { + for (int i = 0; i < ADLIB_CHANNEL_MIDWAY; ++i) + _channels[i].setPtr2(_nullData); + return 0; +} + +int ASound::command3() { + for (int i = 0; i < ADLIB_CHANNEL_MIDWAY; ++i) + _channels[i].enable(0xFF); + return 0; +} + +int ASound::command4() { + for (int i = ADLIB_CHANNEL_MIDWAY; i < ADLIB_CHANNEL_COUNT; ++i) + _channels[i].setPtr2(_nullData); + return 0; +} + +int ASound::command5() { + for (int i = 5; i < ADLIB_CHANNEL_COUNT; ++i) + _channels[i].enable(0xFF); + return 0; +} + +int ASound::command6() { + _v9 = _v1; + _v1 = 0; + _v10 = _v2; + _v2 = 0; + + channelOff(0x43); + channelOff(0x44); + channelOff(0x45); + channelOff(0x4B); + channelOff(0x4C); + channelOff(0x4D); + channelOff(0x53); + channelOff(0x54); + channelOff(0x55); + + return 0; +} + +int ASound::command7() { + channelOn(0x43, _channels[0]._volume); + channelOn(0x44, _channels[1]._volume); + channelOn(0x45, _channels[2]._volume); + channelOn(0x4B, _channels[3]._volume); + channelOn(0x4C, _channels[4]._volume); + channelOn(0x4D, _channels[5]._volume); + + _v1 = _v9; + _v2 = _v10; + + if (_v9 != _v10) + resultCheck(); + + _isDisabled = 0; + return _v10; +} + +int ASound::command8() { + int result = 0; + for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) + result |= _channels[i]._activeCount; + + return result; +} + +/*-----------------------------------------------------------------------*/ + +const ASound1::CommandPtr ASound1::_commandList[42] = { + &ASound1::command0, &ASound1::command1, &ASound1::command2, &ASound1::command3, + &ASound1::command4, &ASound1::command5, &ASound1::command6, &ASound1::command7, + &ASound1::command8, &ASound1::command9, &ASound1::command10, &ASound1::command11, + &ASound1::command12, &ASound1::command13, &ASound1::command14, &ASound1::command15, + &ASound1::command16, &ASound1::command17, &ASound1::command18, &ASound1::command19, + &ASound1::command20, &ASound1::command21, &ASound1::command22, &ASound1::command23, + &ASound1::command24, &ASound1::command25, &ASound1::command26, &ASound1::command27, + &ASound1::command28, &ASound1::command29, &ASound1::command30, &ASound1::command31, + &ASound1::command32, &ASound1::command33, &ASound1::command34, &ASound1::command35, + &ASound1::command36, &ASound1::command37, &ASound1::command38, &ASound1::command39, + &ASound1::command40, &ASound1::command41 +}; + +ASound1::ASound1(Audio::Mixer *mixer): ASound(mixer, "asound.001", 0x1520) { + _cmd23Toggle = false; + + // Load sound samples + _soundFile.seek(_dataOffset + 0x12C); + for (int i = 0; i < 98; ++i) + _samples.push_back(AdlibSample(_soundFile)); +} + +int ASound1::command(int commandId) { + if (commandId > 41) + return 0; + + _frameCounter = 0; + return (this->*_commandList[commandId])(); +} + +int ASound1::command9() { + playSound(0xC68, 12); + return 0; +} + +int ASound1::command10() { + byte *pData1 = loadData(0x130E, 48); + if (!isSoundActive(pData1)) { + command1(); + _channels[0].load(pData1); + _channels[1].load(loadData(0x133E, 392)); + _channels[2].load(loadData(0x14C6, 46)); + _channels[3].load(loadData(0x14F4, 48)); + } + + return 0; +} + +int ASound1::command11() { + command111213(); + _channels[0]._field1E = 0; + _channels[1]._field1E = 0; + return 0; +} + +int ASound1::command12() { + command111213(); + _channels[0]._field1E = 40; + _channels[1]._field1E = 0; + return 0; +} + +int ASound1::command13() { + command111213(); + _channels[0]._field1E = 40; + _channels[1]._field1E = 50; + return 0; +} + +int ASound1::command14() { + playSound(0x1216, 248); + return 0; +} + +int ASound1::command15() { + byte *pData1 = loadData(0x1524, 152); + if (!isSoundActive(pData1)) { + command1(); + _channels[4].load(pData1); + _channels[5].load(loadData(0x15BC, 94)); + _channels[6].load(loadData(0x161A, 94)); + _channels[7].load(loadData(0x1678, 42)); + _channels[8].load(loadData(0x16A2, 42)); + } + + return 0; +} + +int ASound1::command16() { + playSound(0xC74, 14); + return 0; +} + +int ASound1::command17() { + playSound(0xE9A, 10); + return 0; +} + +int ASound1::command18() { + command1(); + playSound(0xCA6, 20); + return 0; +} + +int ASound1::command19() { + command1(); + playSound(0xCBA, 74); + return 0; +} + +int ASound1::command20() { + byte *pData = loadData(0xD18, 28); + if (!isSoundActive(pData)) + playSound(pData); + return 0; +} + +int ASound1::command21() { + playSound(0xD04, 20); + return 0; +} + +int ASound1::command22() { + byte *pData = loadData(0xD34, 10); + pData[6] = (getRandomNumber() & 7) + 85; + + if (!isSoundActive(pData)) + playSound(pData); + + return 0; +} + +int ASound1::command23() { + _cmd23Toggle = !_cmd23Toggle; + playSound(_cmd23Toggle ? 0xD3E : 0xD46, 8); + return 0; +} + +int ASound1::command24() { + playSound(0xD4E, 18); + playSound(0xD60, 20); + playSound(0xD74, 14); + return 0; +} + +int ASound1::command25() { + byte *pData = loadData(0xD82, 16); + if (!isSoundActive(pData)) + playSound(pData); + + return 0; +} + +int ASound1::command26() { + error("TODO: command26"); + return 0; +} + +int ASound1::command27() { + error("TODO: ASound::command27"); + return 0; +} + +int ASound1::command28() { + playSound(0xD92, 28); + return 0; +} + +int ASound1::command29() { + error("TODO: ASound::command29"); + return 0; +} + +int ASound1::command30() { + error("TODO: ASound::command30"); + return 0; +} + +int ASound1::command31() { + byte *pData = loadData(0xDAE, 14); + if (!isSoundActive(pData)) + playSound(pData); + + return 0; +} + +int ASound1::command32() { + error("TODO: ASound::command32"); + return 0; +} + +int ASound1::command33() { + playSound(0xDBC, 10); + playSound(0xDC6, 10); + return 0; +} + +int ASound1::command34() { + int v = getRandomNumber() & 0x20; + if (!v) + v = 0x60; + + byte *pData = loadData(0xDD0, 22); + pData[8] = pData[15] = v; + playSound(pData); + return 0; +} + +int ASound1::command35() { + playSound(0xDE6, 16); + return 0; +} + +int ASound1::command36() { + playSound(0xE10, 10); + command34(); + + return 0; +} + +int ASound1::command37() { + playSound(0xE1A, 14); + return 0; +} + +int ASound1::command38() { + playSound(0xE28, 114); + return 0; +} + +int ASound1::command39() { + byte *pData1 = loadData(0x16CC, 82); + if (!isSoundActive(pData1)) { + _channels[5].load(pData1); + _channels[6].load(loadData(0x171E, 30)); + _channels[7].load(loadData(0x173C, 40)); + _channels[8].load(loadData(0x1764, 64)); + } + return 0; +} + +int ASound1::command40() { + playSound(0xDF6, 26); + return 0; +} + +int ASound1::command41() { + playSound(0xC32, 34); + playSound(0xC54, 20); + return 0; +} + +void ASound1::command111213() { + byte *pData1 = loadData(0xEF6, 408); + if (!isSoundActive(pData1)) { + command1(); + _channels[0].load(pData1); + _channels[1].load(loadData(0x108E, 266)); + _channels[2].load(loadData(0x1198, 66)); + _channels[2].load(loadData(0x11DA, 60)); + } +} + +void ASound1::command2627293032() { + // TODO: This method takes a parameter off the stack for several levels up. + // i.e. something the caller's caller pushed onto the stack. Need to figure + // out a better way to pass parameters down if this is actually in use. +} + +} // End of namespace Nebular + +} // End of namespace MADS diff --git a/engines/mads/nebular/sound_nebular.h b/engines/mads/nebular/sound_nebular.h new file mode 100644 index 0000000000..11836e6559 --- /dev/null +++ b/engines/mads/nebular/sound_nebular.h @@ -0,0 +1,408 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef MADS_SOUND_NEBULAR_H +#define MADS_SOUND_NEBULAR_H + +#include "common/scummsys.h" +#include "common/file.h" +#include "common/mutex.h" +#include "common/queue.h" +#include "audio/audiostream.h" +#include "audio/fmopl.h" +#include "audio/mixer.h" + +namespace MADS { + +class SoundManager; + +namespace Nebular { + +/** + * Represents the data for a channel on the Adlib + */ +class AdlibChannel { +public: + int _activeCount; + int _field1; + int _field2; + int _field3; + int _field4; + int _sampleIndex; + int _volume; + int _field7; + int _field8; + int _field9; + int _fieldA; + uint8 _fieldB; + int _fieldC; + int _fieldD; + int _fieldE; + byte *_ptr1; + byte *_pSrc; + byte *_ptr3; + byte *_ptr4; + int _field17; + int _field19; + byte *_soundData; + int _field1D; + int _field1E; + int _field1F; +public: + AdlibChannel(); + + void reset(); + void enable(int flag); + void setPtr2(byte *pData); + void load(byte *pData); + void check(byte *nullPtr); +}; + +class AdlibChannelData { +public: + int _field0; + int _freqMask; + int _freqBase; + int _field6; +}; + +class AdlibSample { +public: + int _attackRate; + int _decayRate; + int _sustainLevel; + int _releaseRate; + bool _egTyp; + bool _ksr; + int _totalLevel; + int _scalingLevel; + int _waveformSelect; + int _freqMultiple; + int _feedback; + bool _ampMod; + int _vib; + int _alg; + int _fieldE; + int _freqMask; + int _freqBase; + int _field14; + + AdlibSample() {} + AdlibSample(Common::SeekableReadStream &s); +}; + +struct RegisterValue { + uint8 _regNum; + uint8 _value; + + RegisterValue(int regNum, int value) { + _regNum = regNum; _value = value; + } +}; + +#define ADLIB_CHANNEL_COUNT 9 +#define ADLIB_CHANNEL_MIDWAY 5 +#define CALLBACKS_PER_SECOND 60 + +/** + * Base class for the sound player resource files + */ +class ASound: public Audio::AudioStream { +private: + struct CachedDataEntry { + int _offset; + byte *_data; + }; + Common::List<CachedDataEntry> _dataCache; + uint16 _randomSeed; + + /** + * Does the initial Adlib initialisation + */ + void adlibInit(); + + /** + * Does on-going processing for the Adlib sounds being played + */ + void update(); + + /** + * Polls each of the channels for updates + */ + void pollChannels(); + + /** + * Checks the status of the channels + */ + void checkChannels(); + + /** + * Polls the currently active channel + */ + void pollActiveChannel(); + + /** + * Updates the octave of the currently active channel + */ + void updateOctave(); + + void updateChannelState(); + void updateActiveChannel(); + + /** + * Loads up the specified sample + */ + void loadSample(int sampleIndex); + + /** + * Writes out the data of the selected sample to the Adlib + */ + void processSample(); + + void updateFNumber(); +protected: + /** + * Queue a byte for an Adlib register + */ + void write(int reg, int val); + + /** + * Queue a byte for an Adlib register, and store it in the _ports array + */ + int write2(int state, int reg, int val); + + /** + * Flush any pending Adlib register values to the OPL driver + */ + void flush(); + + /** + * Turn a channel on + */ + void channelOn(int reg, int volume); + + /** + * Turn a channel off + */ + void channelOff(int reg); + + /** + * Checks for whether a poll result needs to be set + */ + void resultCheck(); + + /** + * Loads a data block from the sound file, caching the result for any future + * calls for the same data + */ + byte *loadData(int offset, int size); + + /** + * Play the specified sound + * @param offset Offset of sound data within sound player data segment + * @param size Size of sound data block + */ + void playSound(int offset, int size); + + /** + * Play the specified raw sound data + * @param pData Pointer to data block containing sound data + */ + void playSound(byte *pData); + + /** + * Checks to see whether the given block of data is already loaded into a channel. + */ + bool isSoundActive(byte *pData); + + /** + * Sets the frequency for a given channel. + */ + void setFrequency(int channel, int freq); + + /** + * Returns a 16-bit random number + */ + int getRandomNumber(); + + int command0(); + int command1(); + int command2(); + int command3(); + int command4(); + int command5(); + int command6(); + int command7(); + int command8(); +public: + Audio::Mixer *_mixer; + FM_OPL *_opl; + Audio::SoundHandle _soundHandle; + AdlibChannel _channels[ADLIB_CHANNEL_COUNT]; + AdlibChannel *_activeChannelPtr; + AdlibChannelData _channelData[11]; + Common::Array<AdlibSample> _samples; + AdlibSample *_samplePtr; + Common::File _soundFile; + Common::Queue<RegisterValue> _queue; + Common::Mutex _driverMutex; + int _dataOffset; + int _frameCounter; + bool _isDisabled; + int _v1; + int _v2; + int _activeChannelNumber; + int _freqMask1; + int _freqMask2; + int _freqBase1; + int _freqBase2; + int _channelNum1, _channelNum2; + int _v7; + int _v8; + int _v9; + int _v10; + int _pollResult; + int _resultFlag; + byte _nullData[2]; + int _ports[256]; + bool _stateFlag; + int _activeChannelReg; + int _v11; + bool _amDep, _vibDep, _splitPoint; + int _samplesPerCallback; + int _samplesPerCallbackRemainder; + int _samplesTillCallback; + int _samplesTillCallbackRemainder; +public: + /** + * Constructor + * @param filename Specifies the adlib sound player file to use + * @param dataOffset Offset in the file of the data segment + */ + ASound(Audio::Mixer *mixer, const Common::String &filename, int dataOffset); + + /** + * Destructor + */ + virtual ~ASound(); + + /** + * Execute a player command. Most commands represent sounds to play, but some + * low number commands also provide control operations + */ + virtual int command(int commandId) = 0; + + /** + * Stop all currently playing sounds + */ + int stop(); + + /** + * Main poll method to allow sounds to progress + */ + int poll(); + + /** + * General noise/note output + */ + void noise(); + + /** + * Return the current frame counter + */ + int getFrameCounter() { return _frameCounter; } + + // AudioStream interface + /** + * Main buffer read + */ + virtual int readBuffer(int16 *buffer, const int numSamples); + + /** + * Mono sound only + */ + virtual bool isStereo() const { return false; } + + /** + * Data is continuously pushed, so definitive end + */ + virtual bool endOfData() const { return false; } + + /** + * Return sample rate + */ + virtual int getRate() const { return 11025; } +}; + +class ASound1: public ASound { +private: + typedef int (ASound1::*CommandPtr)(); + static const CommandPtr _commandList[42]; + bool _cmd23Toggle; + + int command9(); + int command10(); + int command11(); + int command12(); + int command13(); + int command14(); + int command15(); + int command16(); + int command17(); + int command18(); + int command19(); + int command20(); + int command21(); + int command22(); + int command23(); + int command24(); + int command25(); + int command26(); + int command27(); + int command28(); + int command29(); + int command30(); + int command31(); + int command32(); + int command33(); + int command34(); + int command35(); + int command36(); + int command37(); + int command38(); + int command39(); + int command40(); + int command41(); + + void command111213(); + void command2627293032(); +public: + ASound1(Audio::Mixer *mixer); + + virtual int command(int commandId); +}; + +} // End of namespace Nebular + +} // End of namespace MADS + +#endif /* MADS_SOUND_NEBULAR_H */ |