diff options
Diffstat (limited to 'audio/fmopl.cpp')
-rw-r--r-- | audio/fmopl.cpp | 184 |
1 files changed, 161 insertions, 23 deletions
diff --git a/audio/fmopl.cpp b/audio/fmopl.cpp index c18e544410..cc00ace264 100644 --- a/audio/fmopl.cpp +++ b/audio/fmopl.cpp @@ -22,21 +22,33 @@ #include "audio/fmopl.h" +#include "audio/mixer.h" #include "audio/softsynth/opl/dosbox.h" #include "audio/softsynth/opl/mame.h" #include "common/config-manager.h" +#include "common/system.h" #include "common/textconsole.h" +#include "common/timer.h" #include "common/translation.h" namespace OPL { +// Factory functions + +#ifdef USE_ALSA +namespace ALSA { + OPL *create(Config::OplType type); +} // End of namespace ALSA +#endif // USE_ALSA + // Config implementation enum OplEmulator { kAuto = 0, kMame = 1, - kDOSBox = 2 + kDOSBox = 2, + kALSA = 3 }; OPL::OPL() { @@ -51,6 +63,9 @@ const Config::EmulatorDescription Config::_drivers[] = { #ifndef DISABLE_DOSBOX_OPL { "db", _s("DOSBox OPL emulator"), kDOSBox, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 }, #endif +#ifdef USE_ALSA + { "alsa", _s("ALSA Direct FM"), kALSA, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 }, +#endif { 0, 0, 0, 0 } }; @@ -63,6 +78,15 @@ Config::DriverId Config::parse(const Common::String &name) { return -1; } +const Config::EmulatorDescription *Config::findDriver(DriverId id) { + for (int i = 0; _drivers[i].name; ++i) { + if (_drivers[i].id == id) + return &_drivers[i]; + } + + return 0; +} + Config::DriverId Config::detect(OplType type) { uint32 flags = 0; switch (type) { @@ -80,12 +104,21 @@ Config::DriverId Config::detect(OplType type) { } DriverId drv = parse(ConfMan.get("opl_driver")); + if (drv == kAuto) { + // Since the "auto" can be explicitly set for a game, and this + // driver shows up in the GUI as "<default>", check if there is + // a global setting for it before resorting to auto-detection. + drv = parse(ConfMan.get("opl_driver", Common::ConfigManager::kApplicationDomain)); + } // When a valid driver is selected, check whether it supports // the requested OPL chip. if (drv != -1 && drv != kAuto) { + const EmulatorDescription *driverDesc = findDriver(drv); // If the chip is supported, just use the driver. - if ((flags & _drivers[drv].flags)) { + if (!driverDesc) { + warning("The selected OPL driver %d could not be found", drv); + } else if ((flags & driverDesc->flags)) { return drv; } else { // Else we will output a warning and just @@ -145,6 +178,11 @@ OPL *Config::create(DriverId driver, OplType type) { return new DOSBox::OPL(type); #endif +#ifdef USE_ALSA + case kALSA: + return ALSA::create(type); +#endif + default: warning("Unsupported OPL emulator %d", driver); // TODO: Maybe we should add some dummy emulator too, which just outputs @@ -153,43 +191,143 @@ OPL *Config::create(DriverId driver, OplType type) { } } +void OPL::start(TimerCallback *callback, int timerFrequency) { + _callback.reset(callback); + startCallbacks(timerFrequency); +} + +void OPL::stop() { + stopCallbacks(); + _callback.reset(); +} + bool OPL::_hasInstance = false; -} // End of namespace OPL +RealOPL::RealOPL() : _baseFreq(0), _remainingTicks(0) { +} -void OPLDestroy(FM_OPL *OPL) { - delete OPL; +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 OPLResetChip(FM_OPL *OPL) { - OPL->reset(); +void RealOPL::setCallbackFrequency(int timerFrequency) { + stopCallbacks(); + startCallbacks(timerFrequency); } -void OPLWrite(FM_OPL *OPL, int a, int v) { - OPL->write(a, v); +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"); } -unsigned char OPLRead(FM_OPL *OPL, int a) { - return OPL->read(a); +void RealOPL::stopCallbacks() { + g_system->getTimerManager()->removeTimerProc(timerProc); + _baseFreq = 0; + _remainingTicks = 0; } -void OPLWriteReg(FM_OPL *OPL, int r, int v) { - OPL->writeReg(r, v); +void RealOPL::timerProc(void *refCon) { + static_cast<RealOPL *>(refCon)->onTimer(); } -void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length) { - OPL->readBuffer(buffer, length); +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)(); } -FM_OPL *makeAdLibOPL(int rate) { - FM_OPL *opl = OPL::Config::create(); +EmulatedOPL::EmulatedOPL() : + _nextTick(0), + _samplesPerTick(0), + _baseFreq(0), + _handle(new Audio::SoundHandle()) { +} - if (opl) { - if (!opl->init(rate)) { - delete opl; - opl = 0; +EmulatedOPL::~EmulatedOPL() { + // 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 EmulatedOPL::readBuffer(int16 *buffer, const int numSamples) { + const int stereoFactor = isStereo() ? 2 : 1; + int len = numSamples / stereoFactor; + int step; + + do { + step = len; + if (step > (_nextTick >> FIXP_SHIFT)) + step = (_nextTick >> FIXP_SHIFT); + + generateSamples(buffer, step * stereoFactor); + + _nextTick -= step << FIXP_SHIFT; + if (!(_nextTick >> FIXP_SHIFT)) { + if (_callback && _callback->isValid()) + (*_callback)(); + + _nextTick += _samplesPerTick; } - } - return opl; + buffer += step * stereoFactor; + len -= step; + } while (len); + + return numSamples; +} + +int EmulatedOPL::getRate() const { + return g_system->getMixer()->getOutputRate(); } + +void EmulatedOPL::startCallbacks(int timerFrequency) { + setCallbackFrequency(timerFrequency); + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, _handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +void EmulatedOPL::stopCallbacks() { + g_system->getMixer()->stopHandle(*_handle); +} + +void EmulatedOPL::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; +} + +} // End of namespace OPL |