diff options
-rw-r--r-- | audio/fmopl.cpp | 58 | ||||
-rw-r--r-- | audio/fmopl.h | 43 |
2 files changed, 101 insertions, 0 deletions
diff --git a/audio/fmopl.cpp b/audio/fmopl.cpp index 4bc902eba0..b0b3273e82 100644 --- a/audio/fmopl.cpp +++ b/audio/fmopl.cpp @@ -29,6 +29,7 @@ #include "common/config-manager.h" #include "common/system.h" #include "common/textconsole.h" +#include "common/timer.h" #include "common/translation.h" namespace OPL { @@ -185,6 +186,63 @@ void OPL::stop() { bool OPL::_hasInstance = false; +RealOPL::RealOPL() : _baseFreq(0), _remainingTicks(0) { +} + +RealOPL::~RealOPL() { + // 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(); +} + +void RealOPL::setCallbackFrequency(int timerFrequency) { + stopCallbacks(); + startCallbacks(timerFrequency); +} + +void RealOPL::startCallbacks(int timerFrequency) { + _baseFreq = timerFrequency; + assert(_baseFreq > 0); + + // We can't request more a timer faster than 100Hz. We'll handle this by calling + // the proc multiple times in onTimer() later on. + if (timerFrequency > kMaxFreq) + timerFrequency = kMaxFreq; + + _remainingTicks = 0; + g_system->getTimerManager()->installTimerProc(timerProc, 1000000 / timerFrequency, this, "RealOPL"); +} + +void RealOPL::stopCallbacks() { + g_system->getTimerManager()->removeTimerProc(timerProc); + _baseFreq = 0; + _remainingTicks = 0; +} + +void RealOPL::timerProc(void *refCon) { + static_cast<RealOPL *>(refCon)->onTimer(); +} + +void RealOPL::onTimer() { + uint callbacks = 1; + + if (_baseFreq > kMaxFreq) { + // We run faster than our max, so run the callback multiple + // times to approximate the actual timer callback frequency. + uint totalTicks = _baseFreq + _remainingTicks; + callbacks = totalTicks / kMaxFreq; + _remainingTicks = totalTicks % kMaxFreq; + } + + // Call the callback multiple times. The if is on the inside of the + // loop in case the callback removes itself. + for (uint i = 0; i < callbacks; i++) + if (_callback && _callback->isValid()) + (*_callback)(); +} + EmulatedOPL::EmulatedOPL() : _nextTick(0), _samplesPerTick(0), diff --git a/audio/fmopl.h b/audio/fmopl.h index 091e0bd88d..ba0872d87b 100644 --- a/audio/fmopl.h +++ b/audio/fmopl.h @@ -106,8 +106,14 @@ private: static const EmulatorDescription _drivers[]; }; +/** + * The type of the OPL timer callback functor. + */ typedef Common::Functor0<void> TimerCallback; +/** + * A representation of a Yamaha OPL chip. + */ class OPL { private: static bool _hasInstance; @@ -194,6 +200,43 @@ protected: Common::ScopedPtr<TimerCallback> _callback; }; +/** + * An OPL that represents a real OPL, as opposed to an emulated one. + * + * This will use an actual timer instead of using one calculated from + * the number of samples in an AudioStream::readBuffer call. + */ +class RealOPL : public OPL { +public: + RealOPL(); + virtual ~RealOPL(); + + // OPL API + void setCallbackFrequency(int timerFrequency); + +protected: + // OPL API + void startCallbacks(int timerFrequency); + void stopCallbacks(); + +private: + static void timerProc(void *refCon); + void onTimer(); + + uint _baseFreq; + uint _remainingTicks; + + enum { + kMaxFreq = 100 + }; +}; + +/** + * An OPL that represents an emulated OPL. + * + * This will send callbacks based on the number of samples + * decoded in readBuffer(). + */ class EmulatedOPL : public OPL, protected Audio::AudioStream { public: EmulatedOPL(); |