From d4df315e8787f6230aa55b6d8542e7adda89cd48 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sun, 16 Feb 2014 15:22:57 -0500 Subject: MADS: Initial implementation of Adlib sound driver --- engines/mads/configure.engine | 3 + engines/mads/detection.cpp | 171 ++++++ engines/mads/detection_tables.h | 45 ++ engines/mads/mads.cpp | 74 +++ engines/mads/mads.h | 86 +++ engines/mads/module.mk | 15 + engines/mads/sound.cpp | 53 ++ engines/mads/sound.h | 51 ++ engines/mads/sound_nebular.cpp | 1097 +++++++++++++++++++++++++++++++++++++++ engines/mads/sound_nebular.h | 354 +++++++++++++ 10 files changed, 1949 insertions(+) create mode 100644 engines/mads/configure.engine create mode 100644 engines/mads/detection.cpp create mode 100644 engines/mads/detection_tables.h create mode 100644 engines/mads/mads.cpp create mode 100644 engines/mads/mads.h create mode 100644 engines/mads/module.mk create mode 100644 engines/mads/sound.cpp create mode 100644 engines/mads/sound.h create mode 100644 engines/mads/sound_nebular.cpp create mode 100644 engines/mads/sound_nebular.h diff --git a/engines/mads/configure.engine b/engines/mads/configure.engine new file mode 100644 index 0000000000..60d833e9e8 --- /dev/null +++ b/engines/mads/configure.engine @@ -0,0 +1,3 @@ +# This file is included from the main "configure" script +# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] +add_engine mads "Rex Nebular and the Cosmic Gender Bender" no diff --git a/engines/mads/detection.cpp b/engines/mads/detection.cpp new file mode 100644 index 0000000000..4e56655f40 --- /dev/null +++ b/engines/mads/detection.cpp @@ -0,0 +1,171 @@ +/* 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 "mads/mads.h" + +#include "base/plugins.h" +#include "common/savefile.h" +#include "common/str-array.h" +#include "common/memstream.h" +#include "engines/advancedDetector.h" +#include "common/system.h" +#include "graphics/colormasks.h" +#include "graphics/surface.h" + +#define MAX_SAVES 99 + +namespace MADS { + +struct MADSGameDescription { + ADGameDescription desc; +}; + +uint32 MADSEngine::getFeatures() const { + return _gameDescription->desc.flags; +} + +Common::Language MADSEngine::getLanguage() const { + return _gameDescription->desc.language; +} + +Common::Platform MADSEngine::getPlatform() const { + return _gameDescription->desc.platform; +} + +bool MADSEngine::getIsDemo() const { + return _gameDescription->desc.flags & ADGF_DEMO; +} + +} // End of namespace MADS + +static const PlainGameDescriptor MADSGames[] = { + {"MADS", "MADS"}, + {"nebular", "Rex Nebular and the Cosmic Gender Bender"}, + {0, 0} +}; + +#include "mads/detection_tables.h" + +class MADSMetaEngine : public AdvancedMetaEngine { +public: + MADSMetaEngine() : AdvancedMetaEngine(MADS::gameDescriptions, sizeof(MADS::MADSGameDescription), MADSGames) { + _maxScanDepth = 3; + } + + virtual const char *getName() const { + return "MADS Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "MADS (c)"; + } + + virtual bool hasFeature(MetaEngineFeature f) const; + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const; + virtual void removeSaveState(const char *target, int slot) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; +}; + +bool MADSMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail); +} + +bool MADS::MADSEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +bool MADSMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const MADS::MADSGameDescription *gd = (const MADS::MADSGameDescription *)desc; + if (gd) { + *engine = new MADS::MADSEngine(syst, gd); + } + return gd != 0; +} + +SaveStateList MADSMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::StringArray filenames; + Common::String saveDesc; + Common::String pattern = Common::String::format("%s.0??", target); + + filenames = saveFileMan->listSavefiles(pattern); + sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order + + SaveStateList saveList; + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { + const char *ext = strrchr(file->c_str(), '.'); + int slot = ext ? atoi(ext + 1) : -1; + + if (slot >= 0 && slot < MAX_SAVES) { + Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file); + + if (in) { + delete in; + } + } + } + + return saveList; +} + +int MADSMetaEngine::getMaximumSaveSlot() const { + return MAX_SAVES; +} + +void MADSMetaEngine::removeSaveState(const char *target, int slot) const { + Common::String filename = Common::String::format("%s.%03d", target, slot); + g_system->getSavefileManager()->removeSavefile(filename); +} + +SaveStateDescriptor MADSMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::String filename = Common::String::format("%s.%03d", target, slot); + Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename); + + if (f) { + delete f; + + // Create the return descriptor + SaveStateDescriptor desc(slot, ""); + + return desc; + } + + return SaveStateDescriptor(); +} + + +#if PLUGIN_ENABLED_DYNAMIC(MADS) +REGISTER_PLUGIN_DYNAMIC(MADS, PLUGIN_TYPE_ENGINE, MADSMetaEngine); +#else +REGISTER_PLUGIN_STATIC(MADS, PLUGIN_TYPE_ENGINE, MADSMetaEngine); +#endif diff --git a/engines/mads/detection_tables.h b/engines/mads/detection_tables.h new file mode 100644 index 0000000000..7a66029b9b --- /dev/null +++ b/engines/mads/detection_tables.h @@ -0,0 +1,45 @@ +/* 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. + * + */ + +namespace MADS { + +static const MADSGameDescription gameDescriptions[] = { + { + // Rex Nebular and the Cosmic Gender Bender DOS English + { + "nebular", + 0, + { + {"mpslabs.001", 0, "4df5c557b52abb5b661cf4befe5ae301", 1315354}, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + + { AD_TABLE_END_MARKER } +}; + +} // End of namespace MADS diff --git a/engines/mads/mads.cpp b/engines/mads/mads.cpp new file mode 100644 index 0000000000..39ef195cd6 --- /dev/null +++ b/engines/mads/mads.cpp @@ -0,0 +1,74 @@ +/* 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 "mads/mads.h" +#include "mads/sound.h" +#include "common/scummsys.h" +#include "common/config-manager.h" +#include "common/debug-channels.h" +#include "engines/util.h" +#include "common/events.h" + +namespace MADS { + +MADSEngine *g_vm; + +MADSEngine::MADSEngine(OSystem *syst, const MADSGameDescription *gameDesc) : + Engine(syst), _randomSource("MADS") { + DebugMan.addDebugChannel(kDebugPath, "Path", "Pathfinding debug level"); + DebugMan.addDebugChannel(kDebugScripts, "scripts", "Game scripts"); +} + +MADSEngine::~MADSEngine() { +} + +void MADSEngine::initialise() { + _soundManager.setVm(this, _mixer); +} + +static uint32 lastSoundFrame = 0; + +Common::Error MADSEngine::run() { + initGraphics(320, 200, false); + initialise(); + _soundManager.test(); + + Common::Event e; + while (!shouldQuit()) { + g_system->getEventManager()->pollEvent(e); + g_system->delayMillis(10); + + uint32 milli = g_system->getMillis(); + if (milli > (lastSoundFrame + 50)) { + lastSoundFrame = milli; + _soundManager.poll(); + } + } + + return Common::kNoError; +} + +int MADSEngine::getRandomNumber(int maxNumber) { + return _randomSource.getRandomNumber(maxNumber); +} + +} // End of namespace MADS diff --git a/engines/mads/mads.h b/engines/mads/mads.h new file mode 100644 index 0000000000..b54c46ea96 --- /dev/null +++ b/engines/mads/mads.h @@ -0,0 +1,86 @@ +/* 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_MADS_H +#define MADS_MADS_H + +#include "common/scummsys.h" +#include "common/system.h" +#include "common/error.h" +#include "common/random.h" +#include "common/util.h" +#include "engines/engine.h" +#include "graphics/surface.h" +#include "mads/sound.h" + +/** + * This is the namespace of the MADS engine. + * + * Status of this engine: In Development + * + * Games using this engine: + * - Rex Nebular and the Cosmic Gender Bender + */ +namespace MADS { + +#define DEBUG_BASIC 1 +#define DEBUG_INTERMEDIATE 2 +#define DEBUG_DETAILED 3 + +#define MAX_RESOLVE 1000 + +enum MADSDebugChannels { + kDebugPath = 1 << 0, + kDebugScripts = 1 << 1 +}; + + +struct MADSGameDescription; + + +class MADSEngine : public Engine { +private: + const MADSGameDescription *_gameDescription; + Common::RandomSource _randomSource; + SoundManager _soundManager; + + void initialise(); +protected: + // Engine APIs + virtual Common::Error run(); + virtual bool hasFeature(EngineFeature f) const; +public: + MADSEngine(OSystem *syst, const MADSGameDescription *gameDesc); + virtual ~MADSEngine(); + + uint32 getFeatures() const; + Common::Language getLanguage() const; + Common::Platform getPlatform() const; + uint16 getVersion() const; + bool getIsDemo() const; + + int getRandomNumber(int maxNumber); +}; + +} // End of namespace MADS + +#endif /* MADS_MADS_H */ diff --git a/engines/mads/module.mk b/engines/mads/module.mk new file mode 100644 index 0000000000..d8a26a2704 --- /dev/null +++ b/engines/mads/module.mk @@ -0,0 +1,15 @@ +MODULE := engines/mads + +MODULE_OBJS := \ + detection.o \ + sound.o \ + sound_nebular.o \ + mads.o + +# This module can be built as a plugin +ifeq ($(ENABLE_MADS), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/mads/sound.cpp b/engines/mads/sound.cpp new file mode 100644 index 0000000000..335afb4cdb --- /dev/null +++ b/engines/mads/sound.cpp @@ -0,0 +1,53 @@ +/* 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/memstream.h" +#include "mads/sound.h" + +namespace MADS { + +SoundManager::SoundManager() { + _asound = nullptr; +} + +SoundManager::~SoundManager() { + delete _asound; +} + +void SoundManager::setVm(MADSEngine *vm, Audio::Mixer *mixer) { + _vm = vm; + _mixer = mixer; +} + +void SoundManager::test() { + _asound = new Nebular::ASound1(_mixer); + _asound->command(28); +} + +void SoundManager::poll() { + if (_asound) + _asound->poll(); +} + +} // End of namespace MADS diff --git a/engines/mads/sound.h b/engines/mads/sound.h new file mode 100644 index 0000000000..20b56ec4a1 --- /dev/null +++ b/engines/mads/sound.h @@ -0,0 +1,51 @@ +/* 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_H +#define MADS_SOUND_H + +#include "common/scummsys.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "mads/sound_nebular.h" + +namespace MADS { + +class MADSEngine; + +class SoundManager { +private: + MADSEngine *_vm; + Audio::Mixer *_mixer; + Nebular::ASound *_asound; +public: + SoundManager(); + ~SoundManager(); + + void setVm(MADSEngine *vm, Audio::Mixer *mixer); + void test(); + void poll(); +}; + +} // End of namespace MADS + +#endif /* MADS_SOUND_H */ diff --git a/engines/mads/sound_nebular.cpp b/engines/mads/sound_nebular.cpp new file mode 100644 index 0000000000..03fdc5ffae --- /dev/null +++ b/engines/mads/sound_nebular.cpp @@ -0,0 +1,1097 @@ +/* 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/sound_nebular.h" + +namespace MADS { + +namespace Nebular { + +AdlibChannel::AdlibChannel() { + _activeCount = 0; + _field1 = 0; + _field2 = 0; + _field3 = 0; + _field4 = 0; + _field5 = 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; + _soundData = nullptr; // TODO: Check original set it to flag??! + } +} + +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; + } + } +} + +/*-----------------------------------------------------------------------*/ + +SoundData::SoundData(Common::SeekableReadStream &s) { + _field0 = s.readByte(); + _field1 = s.readByte(); + _field2 = s.readByte(); + _field3 = s.readByte(); + _field4 = s.readByte(); + _field5 = s.readByte(); + _field6 = s.readByte(); + _field7 = s.readByte(); + _field8 = s.readByte(); + _field9 = s.readByte(); + _fieldA = s.readByte(); + _fieldB = s.readByte(); + _fieldC = s.readByte(); + _fieldD = s.readByte(); + _fieldE = s.readByte(); + _field10 = s.readUint16LE(); + _field12 = 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()); + + _dataOffset = dataOffset; + _mixer = mixer; + _opl = OPL::Config::create(); + assert(_opl); + _opl->init(11025); + + // Initialise fields + _activeChannelPtr = nullptr; + _soundDataPtr = 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; + + // Initialise the Adlib + adlibInit(); + + // Reset the adlib + command0(); +} + +ASound::~ASound() { + Common::List::iterator i; + for (i = _dataCache.begin(); i != _dataCache.end(); ++i) + delete[] (*i)._data; + + delete _opl; +} + +void ASound::adlibInit() { + // No implementation +} + +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) { + debug("reg %.2x = %.2x", reg, val); + _opl->write(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::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::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() { + bool flag = false; + + AdlibChannel *chan = _activeChannelPtr; + 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 (flag) + 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; + } else { + flag = 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->_field5 = *++pSrc; + chan->_pSrc += 2; + channelProc1(chan->_field5); + 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->_volume = *pSrc >> 1; + flag = 1; + chan->_pSrc += 2; + } else { + 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; + flag = 1; + chan->_pSrc += 2; + } + break; + + case 11: + chan->_fieldD = *++pSrc; + flag = 1; + 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(); + + flag = false; + if (chan->_field9 || chan->_field8) { + if (!--chan->_field9) { + chan->_field9 = chan->_fieldA; + if (chan->_field2) { + chan->_field9 = CLIP(chan->_field2 + chan->_field1E, 0, 63); + } + } + + if (!--chan->_fieldB) { + chan->_fieldB = chan->_fieldC; + if (chan->_field3) { + chan->_fieldD = chan->_field3; + flag = true; + } + } + + if (flag) + 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 v = (_activeChannelPtr->_field4 + _activeChannelPtr->_field1F) / 12; + int vHi = v >> 8; + int vLo = v & 0xFF; + int val = _vList1[vHi] + vHi; + write2(8, reg, val & 0xFF); + + reg += 0x10; + write2(8, reg, (_ports[reg] & 0x20) | (vLo << 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); + + // TODO: Double-check _v5660 = 5600h + 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::channelProc1(int recIndex) { + _activeChannelReg = 0xB0 + _activeChannelNumber; + write2(8, _activeChannelReg, _ports[_activeChannelReg] & 0xDF); + + _activeChannelReg = _activeChannelNumber; + _soundDataPtr = &_soundData[recIndex * 2]; + _v11 = outputChannels[outputIndexes[_activeChannelReg * 2 - 1]]; + channelProc2(); + + AdlibChannelData &cd = _channelData[_activeChannelNumber]; + cd._field6 = _soundDataPtr->_field14; + cd._freqBase = _soundDataPtr->_field12; + cd._freqMask = _soundDataPtr->_field10; + cd._field0 = _soundDataPtr->_fieldE; + + _soundDataPtr = &_soundData[recIndex * 2 + 1]; + _v11 = outputChannels[outputIndexes[_activeChannelReg * 2]]; + channelProc2(); +} + +void ASound::channelProc2() { + // 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); + + int val = _soundDataPtr->_fieldA; + val = (val << 1) | (1 - _soundDataPtr->_fieldD); + write2(8, 0xC0 + _activeChannelReg, val); + + val = (_soundDataPtr->_field0 << 4) | (_soundDataPtr->_field1 & 0xF); + write2(8, 0x60 + _v11, val); + + val = (_soundDataPtr->_field2 << 4) | (_soundDataPtr->_field3 & 0xF); + write2(8, 0x80 + _v11, val); + + val = (_soundDataPtr->_fieldB ? 0x80 : 0) | (_soundDataPtr->_fieldC ? 0x40 : 0) + | (_soundDataPtr->_field4 ? 0x20 : 0) | (_soundDataPtr->_field5 ? 0x10 : 0) + | (_soundDataPtr->_field9 & 0xF); + write2(8, 0x20 + _v11, val); + + write2(8, 0xE0 + _v11, _soundDataPtr->_field8 & 3); + + val = -(_soundDataPtr->_field6 & 0x3F - 0x3F) | (_soundDataPtr->_field7 << 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::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; + + _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 data + _soundFile.seek(_dataOffset + 0x12C); + for (int i = 0; i < 98; ++i) + _soundData.push_back(SoundData(_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() { + playSound(0xCA6, 20); + return 0; +} + +int ASound1::command19() { + 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/sound_nebular.h b/engines/mads/sound_nebular.h new file mode 100644 index 0000000000..8f92389ee1 --- /dev/null +++ b/engines/mads/sound_nebular.h @@ -0,0 +1,354 @@ +/* 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 "audio/audiostream.h" +#include "audio/fmopl.h" +#include "audio/mixer.h" + +namespace MADS { + +namespace Nebular { + +class SoundManager; + +/** + * Represents the data for a channel on the Adlib + */ +class AdlibChannel { +public: + int _activeCount; + int _field1; + int _field2; + int _field3; + int _field4; + int _field5; + int _volume; + int _field7; + int _field8; + int _field9; + int _fieldA; + int _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 SoundData { +public: + int _field0; + int _field1; + int _field2; + int _field3; + int _field4; + int _field5; + int _field6; + int _field7; + int _field8; + int _field9; + int _fieldA; + int _fieldB; + int _fieldC; + int _fieldD; + int _fieldE; + int _field10; + int _field12; + int _field14; + + SoundData() {} + SoundData(Common::SeekableReadStream &s); +}; + +#define ADLIB_CHANNEL_COUNT 9 +#define ADLIB_CHANNEL_MIDWAY 5 + +/** + * Base class for the sound player resource files + */ +class ASound { +private: + struct CachedDataEntry { + int _offset; + byte *_data; + }; + Common::List _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(); + void channelProc1(int recIndex); + void channelProc2(); + void updateFNumber(); +protected: + /** + * Write a byte to an Adlib register + */ + void write(int reg, int val); + + /** + * Write a byte to an Adlib register, and store it in the _ports array + */ + int write2(int state, int reg, int val); + + /** + * 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; + AdlibChannel _channels[ADLIB_CHANNEL_COUNT]; + AdlibChannel *_activeChannelPtr; + AdlibChannelData _channelData[11]; + Common::Array _soundData; + SoundData *_soundDataPtr; + Common::File _soundFile; + 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; +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; } +}; + +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 */ -- cgit v1.2.3