diff --git a/audio/cms.cpp b/audio/cms.cpp new file mode 100644 index 00000000000..64dc2d359f2 --- /dev/null +++ b/audio/cms.cpp @@ -0,0 +1,162 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "audio/cms.h" + +namespace CMS { + +CMS *Config::create() { + // For now this is fixed to the DOSBox emulator. + return new DOSBoxCMS(); +} + +bool CMS::_hasInstance = false; + +CMS::CMS() { + if (_hasInstance) + error("There are multiple CMS output instances running."); + _hasInstance = true; +} + +CMS::~CMS() { + _hasInstance = false; +} + +void CMS::start(TimerCallback *callback, int timerFrequency) { + _callback.reset(callback); + startCallbacks(timerFrequency); +} + +void CMS::stop() { + stopCallbacks(); + _callback.reset(); +} + +EmulatedCMS::EmulatedCMS() : + _nextTick(0), + _samplesPerTick(0), + _baseFreq(0), + _handle(new Audio::SoundHandle()) { } + +EmulatedCMS::~EmulatedCMS() { + // Stop callbacks, just in case. If it's still playing at this + // point, there's probably a bigger issue, though. The subclass + // needs to call stop() or the pointer can still use be used in + // the mixer thread at the same time. + stop(); + + delete _handle; +} + +int EmulatedCMS::readBuffer(int16 *buffer, const int numSamples) { + int len = numSamples / 2; + int step; + + do { + step = len; + if (step > (_nextTick >> FIXP_SHIFT)) + step = (_nextTick >> FIXP_SHIFT); + + generateSamples(buffer, step); + + _nextTick -= step << FIXP_SHIFT; + if (!(_nextTick >> FIXP_SHIFT)) { + if (_callback && _callback->isValid()) + (*_callback)(); + + _nextTick += _samplesPerTick; + } + + buffer += step * 2; + len -= step; + } while (len); + + return numSamples; +} + +int EmulatedCMS::getRate() const { + return g_system->getMixer()->getOutputRate(); +} + +bool EmulatedCMS::endOfData() const { + return false; +} + +bool EmulatedCMS::isStereo() const { + return true; +} + +void EmulatedCMS::startCallbacks(int timerFrequency) { + setCallbackFrequency(timerFrequency); + g_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, _handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +void EmulatedCMS::stopCallbacks() { + g_system->getMixer()->stopHandle(*_handle); +} + +void EmulatedCMS::setCallbackFrequency(int timerFrequency) { + _baseFreq = timerFrequency; + assert(_baseFreq != 0); + + int d = getRate() / _baseFreq; + int r = getRate() % _baseFreq; + + // This is equivalent to (getRate() << FIXP_SHIFT) / BASE_FREQ + // but less prone to arithmetic overflow. + + _samplesPerTick = (d << FIXP_SHIFT) + (r << FIXP_SHIFT) / _baseFreq; +} + +DOSBoxCMS::DOSBoxCMS() : _cms(nullptr) { } + +DOSBoxCMS::~DOSBoxCMS() { + if (_cms) + delete _cms; +} + +bool DOSBoxCMS::init() { + _cms = new CMSEmulator(getRate()); + return _cms != nullptr; +} + +void DOSBoxCMS::reset() { + _cms->reset(); +} + +void DOSBoxCMS::write(int a, int v) { + _cms->portWrite(a, v); +} + +void DOSBoxCMS::writeReg(int r, int v) { + int address = 0x220; + if (r >= 0x100) + address += 0x002; + + _cms->portWrite(address + 1, r & 0x1F); + _cms->portWrite(address, v); +} + +void DOSBoxCMS::generateSamples(int16 *buffer, int numSamples) { + _cms->readBuffer(buffer, numSamples); +} + +} // End of namespace CMS diff --git a/audio/cms.h b/audio/cms.h new file mode 100644 index 00000000000..fce144646f4 --- /dev/null +++ b/audio/cms.h @@ -0,0 +1,194 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef AUDIO_CMS_H +#define AUDIO_CMS_H + +#include "common/func.h" +#include "common/ptr.h" + +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/softsynth/cms.h" + +namespace CMS { + +class CMS; + +class Config { +public: + /** + * Creates a CMS driver. + */ + static CMS *create(); +}; + +/** + * The type of the CMS timer callback functor. + */ +typedef Common::Functor0 TimerCallback; + +class CMS { +private: + static bool _hasInstance; + +public: + // The default number of timer callbacks per second. + static const int DEFAULT_CALLBACK_FREQUENCY = 250; + + CMS(); + virtual ~CMS(); + + /** + * Initializes the CMS emulator. + * + * @return true on success, false on failure + */ + virtual bool init() = 0; + + /** + * Reinitializes the CMS emulator + */ + virtual void reset() = 0; + + /** + * Writes a byte to the given I/O port. CMS responds to 2 sets of 2 ports: + * 0x220/0x222 - value for the 1st/2nd chip (channels 0-5/6-B) + * 0x221/0x223 - register for the 1st/2nd chip + * + * @param a port address + * @param v value, which will be written + */ + virtual void write(int a, int v) = 0; + + /** + * Function to directly write to a specific CMS register. We allow writing + * to secondary CMS chip registers by using register values >= 0x100. + * + * @param r hardware register number to write to + * @param v value, which will be written + */ + virtual void writeReg(int r, int v) = 0; + + /** + * Start the CMS with callbacks. + */ + void start(TimerCallback *callback, int timerFrequency = DEFAULT_CALLBACK_FREQUENCY); + + /** + * Stop the CMS + */ + void stop(); + + /** + * Change the callback frequency. This must only be called from a + * timer proc. + */ + virtual void setCallbackFrequency(int timerFrequency) = 0; + +protected: + /** + * Start the callbacks. + */ + virtual void startCallbacks(int timerFrequency) = 0; + + /** + * Stop the callbacks. + */ + virtual void stopCallbacks() = 0; + + /** + * The functor for callbacks. + */ + Common::ScopedPtr _callback; +}; + +/** + * A CMS that represents an emulated CMS. + * + * This will send callbacks based on the number of samples + * decoded in readBuffer(). + */ +class EmulatedCMS : public CMS, protected Audio::AudioStream { +protected: + static const int FIXP_SHIFT = 16; + +public: + EmulatedCMS(); + virtual ~EmulatedCMS(); + + // CMS API + void setCallbackFrequency(int timerFrequency) override; + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples) override; + int getRate() const override; + bool endOfData() const override; + bool isStereo() const override; + +protected: + // CMS API + void startCallbacks(int timerFrequency) override; + void stopCallbacks() override; + + /** + * Read up to 'length' samples. + * + * Data will be in native endianess, 16 bit per sample, signed. buffer will + * be filled with interleaved left and right channel samples, starting with + * a left sample. The requested number of samples is stereo samples, so if + * you request 2 samples, you will get a total of two left channel and two + * right channel samples. + */ + virtual void generateSamples(int16 *buffer, int numSamples) = 0; + +private: + int _baseFreq; + + int _nextTick; + int _samplesPerTick; + + Audio::SoundHandle *_handle; +}; + +/** + * A CMS that represents the DOSBox CMS emulator. + */ +class DOSBoxCMS : public EmulatedCMS { +public: + DOSBoxCMS(); + ~DOSBoxCMS() override; + + bool init() override; + void reset() override; + void write(int a, int v) override; + void writeReg(int r, int v) override; + +protected: + void generateSamples(int16 *buffer, int numSamples) override; + +private: + CMSEmulator *_cms; +}; + +} // End of namespace CMS + +#endif diff --git a/audio/module.mk b/audio/module.mk index 8519db4438c..5f670ab18ef 100644 --- a/audio/module.mk +++ b/audio/module.mk @@ -5,6 +5,7 @@ MODULE_OBJS := \ adlib_ms.o \ audiostream.o \ casio.o \ + cms.o \ fmopl.o \ mididrv.o \ mididrv_ms.o \ diff --git a/audio/softsynth/cms.cpp b/audio/softsynth/cms.cpp index 4426b3b8aca..1c390153270 100644 --- a/audio/softsynth/cms.cpp +++ b/audio/softsynth/cms.cpp @@ -124,6 +124,10 @@ void CMSEmulator::readBuffer(int16 *buffer, const int numSamples) { update(1, &buffer[0], numSamples); } +void CMSEmulator::reset() { + memset(_saa1099, 0, sizeof(SAA1099) * 2); +} + void CMSEmulator::envelope(int chip, int ch) { SAA1099 *saa = &_saa1099[chip]; if (saa->env_enable[ch]) { diff --git a/audio/softsynth/cms.h b/audio/softsynth/cms.h index 7acdd01543b..950c272b6c2 100644 --- a/audio/softsynth/cms.h +++ b/audio/softsynth/cms.h @@ -77,6 +77,8 @@ public: void portWrite(int port, int val); void readBuffer(int16 *buffer, const int numSamples); + void reset(); + private: uint32 _sampleRate;