ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

fmopl.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "audio/fmopl.h"
00024 
00025 #include "audio/mixer.h"
00026 #include "audio/softsynth/opl/dosbox.h"
00027 #include "audio/softsynth/opl/mame.h"
00028 
00029 #include "common/config-manager.h"
00030 #include "common/system.h"
00031 #include "common/textconsole.h"
00032 #include "common/timer.h"
00033 #include "common/translation.h"
00034 
00035 namespace OPL {
00036 
00037 // Factory functions
00038 
00039 #ifdef USE_ALSA
00040 namespace ALSA {
00041     OPL *create(Config::OplType type);
00042 } // End of namespace ALSA
00043 #endif // USE_ALSA
00044 
00045 // Config implementation
00046 
00047 enum OplEmulator {
00048     kAuto = 0,
00049     kMame = 1,
00050     kDOSBox = 2,
00051     kALSA = 3
00052 };
00053 
00054 OPL::OPL() {
00055     if (_hasInstance)
00056         error("There are multiple OPL output instances running");
00057     _hasInstance = true;
00058 }
00059 
00060 const Config::EmulatorDescription Config::_drivers[] = {
00061     { "auto", "<default>", kAuto, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 },
00062     { "mame", _s("MAME OPL emulator"), kMame, kFlagOpl2 },
00063 #ifndef DISABLE_DOSBOX_OPL
00064     { "db", _s("DOSBox OPL emulator"), kDOSBox, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 },
00065 #endif
00066 #ifdef USE_ALSA
00067     { "alsa", _s("ALSA Direct FM"), kALSA, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 },
00068 #endif
00069     { 0, 0, 0, 0 }
00070 };
00071 
00072 Config::DriverId Config::parse(const Common::String &name) {
00073     for (int i = 0; _drivers[i].name; ++i) {
00074         if (name.equalsIgnoreCase(_drivers[i].name))
00075             return _drivers[i].id;
00076     }
00077 
00078     return -1;
00079 }
00080 
00081 const Config::EmulatorDescription *Config::findDriver(DriverId id) {
00082     for (int i = 0; _drivers[i].name; ++i) {
00083         if (_drivers[i].id == id)
00084             return &_drivers[i];
00085     }
00086 
00087     return 0;
00088 }
00089 
00090 Config::DriverId Config::detect(OplType type) {
00091     uint32 flags = 0;
00092     switch (type) {
00093     case kOpl2:
00094         flags = kFlagOpl2;
00095         break;
00096 
00097     case kDualOpl2:
00098         flags = kFlagDualOpl2;
00099         break;
00100 
00101     case kOpl3:
00102         flags = kFlagOpl3;
00103         break;
00104     }
00105 
00106     DriverId drv = parse(ConfMan.get("opl_driver"));
00107     if (drv == kAuto) {
00108         // Since the "auto" can be explicitly set for a game, and this
00109         // driver shows up in the GUI as "<default>", check if there is
00110         // a global setting for it before resorting to auto-detection.
00111         drv = parse(ConfMan.get("opl_driver", Common::ConfigManager::kApplicationDomain));
00112     }
00113 
00114     // When a valid driver is selected, check whether it supports
00115     // the requested OPL chip.
00116     if (drv != -1 && drv != kAuto) {
00117         const EmulatorDescription *driverDesc = findDriver(drv);
00118         // If the chip is supported, just use the driver.
00119         if (!driverDesc) {
00120             warning("The selected OPL driver %d could not be found", drv);
00121         } else if ((flags & driverDesc->flags)) {
00122             return drv;
00123         } else {
00124             // Else we will output a warning and just
00125             // return that no valid driver is found.
00126             warning("Your selected OPL driver \"%s\" does not support type %d emulation, which is requested by your game", _drivers[drv].description, type);
00127             return -1;
00128         }
00129     }
00130 
00131     // Detect the first matching emulator
00132     drv = -1;
00133 
00134     for (int i = 1; _drivers[i].name; ++i) {
00135         if (_drivers[i].flags & flags) {
00136             drv = _drivers[i].id;
00137             break;
00138         }
00139     }
00140 
00141     return drv;
00142 }
00143 
00144 OPL *Config::create(OplType type) {
00145     return create(kAuto, type);
00146 }
00147 
00148 OPL *Config::create(DriverId driver, OplType type) {
00149     // On invalid driver selection, we try to do some fallback detection
00150     if (driver == -1) {
00151         warning("Invalid OPL driver selected, trying to detect a fallback emulator");
00152         driver = kAuto;
00153     }
00154 
00155     // If autodetection is selected, we search for a matching
00156     // driver.
00157     if (driver == kAuto) {
00158         driver = detect(type);
00159 
00160         // No emulator for the specified OPL chip could
00161         // be found, thus stop here.
00162         if (driver == -1) {
00163             warning("No OPL emulator available for type %d", type);
00164             return 0;
00165         }
00166     }
00167 
00168     switch (driver) {
00169     case kMame:
00170         if (type == kOpl2)
00171             return new MAME::OPL();
00172         else
00173             warning("MAME OPL emulator only supports OPL2 emulation");
00174         return 0;
00175 
00176 #ifndef DISABLE_DOSBOX_OPL
00177     case kDOSBox:
00178         return new DOSBox::OPL(type);
00179 #endif
00180 
00181 #ifdef USE_ALSA
00182     case kALSA:
00183         return ALSA::create(type);
00184 #endif
00185 
00186     default:
00187         warning("Unsupported OPL emulator %d", driver);
00188         // TODO: Maybe we should add some dummy emulator too, which just outputs
00189         // silence as sound?
00190         return 0;
00191     }
00192 }
00193 
00194 void OPL::start(TimerCallback *callback, int timerFrequency) {
00195     _callback.reset(callback);
00196     startCallbacks(timerFrequency);
00197 }
00198 
00199 void OPL::stop() {
00200     stopCallbacks();
00201     _callback.reset();
00202 }
00203 
00204 bool OPL::_hasInstance = false;
00205 
00206 RealOPL::RealOPL() : _baseFreq(0), _remainingTicks(0) {
00207 }
00208 
00209 RealOPL::~RealOPL() {
00210     // Stop callbacks, just in case. If it's still playing at this
00211     // point, there's probably a bigger issue, though. The subclass
00212     // needs to call stop() or the pointer can still use be used in
00213     // the mixer thread at the same time.
00214     stop();
00215 }
00216 
00217 void RealOPL::setCallbackFrequency(int timerFrequency) {
00218     stopCallbacks();
00219     startCallbacks(timerFrequency);
00220 }
00221 
00222 void RealOPL::startCallbacks(int timerFrequency) {
00223     _baseFreq = timerFrequency;
00224     assert(_baseFreq > 0);
00225 
00226     // We can't request more a timer faster than 100Hz. We'll handle this by calling
00227     // the proc multiple times in onTimer() later on.
00228     if (timerFrequency > kMaxFreq)
00229         timerFrequency = kMaxFreq;
00230 
00231     _remainingTicks = 0;
00232     g_system->getTimerManager()->installTimerProc(timerProc, 1000000 / timerFrequency, this, "RealOPL");
00233 }
00234 
00235 void RealOPL::stopCallbacks() {
00236     g_system->getTimerManager()->removeTimerProc(timerProc);
00237     _baseFreq = 0;
00238     _remainingTicks = 0;
00239 }
00240 
00241 void RealOPL::timerProc(void *refCon) {
00242     static_cast<RealOPL *>(refCon)->onTimer();
00243 }
00244 
00245 void RealOPL::onTimer() {
00246     uint callbacks = 1;
00247 
00248     if (_baseFreq > kMaxFreq) {
00249         // We run faster than our max, so run the callback multiple
00250         // times to approximate the actual timer callback frequency.
00251         uint totalTicks = _baseFreq + _remainingTicks;
00252         callbacks = totalTicks / kMaxFreq;
00253         _remainingTicks = totalTicks % kMaxFreq;
00254     }
00255 
00256     // Call the callback multiple times. The if is on the inside of the
00257     // loop in case the callback removes itself.
00258     for (uint i = 0; i < callbacks; i++)
00259         if (_callback && _callback->isValid())
00260             (*_callback)();
00261 }
00262 
00263 EmulatedOPL::EmulatedOPL() :
00264     _nextTick(0),
00265     _samplesPerTick(0),
00266     _baseFreq(0),
00267     _handle(new Audio::SoundHandle()) {
00268 }
00269 
00270 EmulatedOPL::~EmulatedOPL() {
00271     // Stop callbacks, just in case. If it's still playing at this
00272     // point, there's probably a bigger issue, though. The subclass
00273     // needs to call stop() or the pointer can still use be used in
00274     // the mixer thread at the same time.
00275     stop();
00276 
00277     delete _handle;
00278 }
00279 
00280 int EmulatedOPL::readBuffer(int16 *buffer, const int numSamples) {
00281     const int stereoFactor = isStereo() ? 2 : 1;
00282     int len = numSamples / stereoFactor;
00283     int step;
00284 
00285     do {
00286         step = len;
00287         if (step > (_nextTick >> FIXP_SHIFT))
00288             step = (_nextTick >> FIXP_SHIFT);
00289 
00290         generateSamples(buffer, step * stereoFactor);
00291 
00292         _nextTick -= step << FIXP_SHIFT;
00293         if (!(_nextTick >> FIXP_SHIFT)) {
00294             if (_callback && _callback->isValid())
00295                 (*_callback)();
00296 
00297             _nextTick += _samplesPerTick;
00298         }
00299 
00300         buffer += step * stereoFactor;
00301         len -= step;
00302     } while (len);
00303 
00304     return numSamples;
00305 }
00306 
00307 int EmulatedOPL::getRate() const {
00308     return g_system->getMixer()->getOutputRate();
00309 }
00310 
00311 void EmulatedOPL::startCallbacks(int timerFrequency) {
00312     setCallbackFrequency(timerFrequency);
00313     g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, _handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
00314 }
00315 
00316 void EmulatedOPL::stopCallbacks() {
00317     g_system->getMixer()->stopHandle(*_handle);
00318 }
00319 
00320 void EmulatedOPL::setCallbackFrequency(int timerFrequency) {
00321     _baseFreq = timerFrequency;
00322     assert(_baseFreq != 0);
00323 
00324     int d = getRate() / _baseFreq;
00325     int r = getRate() % _baseFreq;
00326 
00327     // This is equivalent to (getRate() << FIXP_SHIFT) / BASE_FREQ
00328     // but less prone to arithmetic overflow.
00329 
00330     _samplesPerTick = (d << FIXP_SHIFT) + (r << FIXP_SHIFT) / _baseFreq;
00331 }
00332 
00333 } // End of namespace OPL


Generated on Sat May 25 2019 05:00:45 for ResidualVM by doxygen 1.7.1
curved edge   curved edge