aboutsummaryrefslogtreecommitdiff
path: root/engines/mads/nebular
diff options
context:
space:
mode:
authorPaul Gilbert2014-02-19 23:21:05 -0500
committerPaul Gilbert2014-02-19 23:21:05 -0500
commitde384bae2ef1b904224b0fe00326e5a0597aafb6 (patch)
treebcc1f56dfb9c729b5de0812fcb5e4412007e09b8 /engines/mads/nebular
parent58bb1383d0b11d357128ca2b0a7634f091c0fb5b (diff)
downloadscummvm-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.cpp1180
-rw-r--r--engines/mads/nebular/sound_nebular.h408
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 */