From 021a828e2ecef816c2079bfcb99aa8d6e6095c47 Mon Sep 17 00:00:00 2001 From: Coen Rampen Date: Sun, 15 May 2022 18:21:42 +0200 Subject: [PATCH] AUDIO: Decouple CMS emulator from engines This commit adds a mediator class to decouple the CMS emulator from the engines that use it. This allows for easier updating or replacement of the emulator or addition of new emulators. It also enables using the emulator via a callback mechanism. --- audio/cms.cpp | 162 +++++++++++++++++++++++++++++++++ audio/cms.h | 194 ++++++++++++++++++++++++++++++++++++++++ audio/module.mk | 1 + audio/softsynth/cms.cpp | 4 + audio/softsynth/cms.h | 2 + 5 files changed, 363 insertions(+) create mode 100644 audio/cms.cpp create mode 100644 audio/cms.h 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;