diff options
author | Johannes Schickel | 2009-05-05 21:47:12 +0000 |
---|---|---|
committer | Johannes Schickel | 2009-05-05 21:47:12 +0000 |
commit | 08581ed69811d50e20eab2a3b491a49b0a494d60 (patch) | |
tree | 51aee65255a47ba721aaee5f52770d0453fad2a2 /sound/softsynth/opl/dosbox.cpp | |
parent | d9e0499a36004023dea3dfd95312b1d91eca5bbc (diff) | |
download | scummvm-rg350-08581ed69811d50e20eab2a3b491a49b0a494d60.tar.gz scummvm-rg350-08581ed69811d50e20eab2a3b491a49b0a494d60.tar.bz2 scummvm-rg350-08581ed69811d50e20eab2a3b491a49b0a494d60.zip |
AdLib emulator changes part2:
- Add new OPL emulator API (and legacy access API) in sound/fmopl.h
- Add DOSBox OPL emulator.
- Update MAME OPL emulator for the API changes.
svn-id: r40334
Diffstat (limited to 'sound/softsynth/opl/dosbox.cpp')
-rw-r--r-- | sound/softsynth/opl/dosbox.cpp | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/sound/softsynth/opl/dosbox.cpp b/sound/softsynth/opl/dosbox.cpp new file mode 100644 index 0000000000..60bc417cdd --- /dev/null +++ b/sound/softsynth/opl/dosbox.cpp @@ -0,0 +1,355 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +/* + * Based on AdLib emulation code of DOSBox + * Copyright (C) 2002-2009 The DOSBox Team + * Licensed under GPLv2+ + * http://www.dosbox.com + */ + +#ifndef DISABLE_DOSBOX_OPL + +#include "dosbox.h" + +#include "common/system.h" + +#include <math.h> +#include <string.h> + +namespace OPL { +namespace DOSBox { + +Timer::Timer() { + masked = false; + overflow = false; + enabled = false; + counter = 0; + delay = 0; +} + +void Timer::update(double time) { + if (!enabled || !delay) + return; + double deltaStart = time - startTime; + // Only set the overflow flag when not masked + if (deltaStart >= 0 && !masked) + overflow = 1; +} + +void Timer::reset(double time) { + overflow = false; + if (!delay || !enabled) + return; + double delta = (time - startTime); + double rem = fmod(delta, delay); + double next = delay - rem; + startTime = time + next; +} + +void Timer::stop() { + enabled = false; +} + +void Timer::start(double time, int scale) { + //Don't enable again + if (enabled) + return; + enabled = true; + delay = 0.001 * (256 - counter) * scale; + startTime = time + delay; +} + +bool Chip::write(uint32 reg, uint8 val) { + switch (reg) { + case 0x02: + timer[0].counter = val; + return true; + case 0x03: + timer[1].counter = val; + return true; + case 0x04: + double time = g_system->getMillis() / 1000.0; + + if (val & 0x80) { + timer[0].reset(time); + timer[1].reset(time); + } else { + timer[0].update(time); + timer[1].update(time); + + if (val & 0x1) + timer[0].start(time, 80); + else + timer[0].stop(); + + timer[0].masked = (val & 0x40) > 0; + + if (timer[0].masked) + timer[0].overflow = false; + + if (val & 0x2) + timer[1].start(time, 320); + else + timer[1].stop(); + + timer[1].masked = (val & 0x20) > 0; + + if (timer[1].masked) + timer[1].overflow = false; + } + return true; + } + return false; +} + +uint8 Chip::read() { + double time = g_system->getMillis() / 1000.0; + + timer[0].update(time); + timer[1].update(time); + + uint8 ret = 0; + // Overflow won't be set if a channel is masked + if (timer[0].overflow) { + ret |= 0x40; + ret |= 0x80; + } + if (timer[1].overflow) { + ret |= 0x20; + ret |= 0x80; + } + return ret; +} + +namespace OPL2 { +#include "opl.cpp" + +struct Handler : public DOSBox::Handler { + void writeReg(uint32 reg, uint8 val) { + adlib_write(reg, val); + } + + uint32 writeAddr(uint32 port, uint8 val) { + return val; + } + + void generate(int16 *chan, uint samples) { + adlib_getsample(chan, samples); + } + + void init(uint rate) { + adlib_init(rate); + } +}; +} // end of namespace OPL2 + +namespace OPL3 { +#define OPLTYPE_IS_OPL3 +#include "opl.cpp" + +struct Handler : public DOSBox::Handler { + void writeReg(uint32 reg, uint8 val) { + adlib_write(reg,val); + } + + uint32 writeAddr(uint32 port, uint8 val) { + adlib_write_index(port, val); + return index; + } + + void generate(int16 *chan, uint samples) { + adlib_getsample(chan, samples); + } + + void init(uint rate) { + adlib_init(rate); + } +}; +} // end of namespace OPL3 + +OPL_DOSBox::OPL_DOSBox(kOplType type) : _type(type), _rate(0), _handler(0) { +} + +OPL_DOSBox::~OPL_DOSBox() { + free(); +} + +void OPL_DOSBox::free() { + delete _handler; + _handler = 0; +} + +bool OPL_DOSBox::init(int rate) { + free(); + + memset(&_reg, 0, sizeof(_reg)); + memset(_chip, 0, sizeof(_chip)); + + switch (_type) { + case kOpl2: + _handler = new OPL2::Handler(); + break; + + case kDualOpl2: + case kOpl3: + _handler = new OPL3::Handler(); + break; + + default: + return false; + } + + _handler->init(rate); + _rate = rate; + return true; +} + +void OPL_DOSBox::reset() { + init(_rate); +} + +void OPL_DOSBox::write(int port, int val) { + if (port&1) { + switch (_type) { + case kOpl2: + case kOpl3: + if (!_chip[0].write(_reg.normal, val)) + _handler->writeReg(_reg.normal, val); + break; + case kDualOpl2: + // Not a 0x??8 port, then write to a specific port + if (!(port & 0x8)) { + byte index = (port & 2) >> 1; + dualWrite(index, _reg.dual[index], val); + } else { + //Write to both ports + dualWrite(0, _reg.dual[0], val); + dualWrite(1, _reg.dual[1], val); + } + break; + } + } else { + // Ask the handler to write the address + // Make sure to clip them in the right range + switch (_type) { + case kOpl2: + _reg.normal = _handler->writeAddr(port, val) & 0xff; + break; + case kOpl3: + _reg.normal = _handler->writeAddr(port, val) & 0x1ff; + break; + case kDualOpl2: + // Not a 0x?88 port, when write to a specific side + if (!(port & 0x8)) { + byte index = (port & 2) >> 1; + _reg.dual[index] = val & 0xff; + } else { + _reg.dual[0] = val & 0xff; + _reg.dual[1] = val & 0xff; + } + break; + } + } +} + +byte OPL_DOSBox::read(int port) { + switch (_type) { + case kOpl2: + if (!(port & 1)) + //Make sure the low bits are 6 on opl2 + return _chip[0].read() | 0x6; + break; + case kOpl3: + if (!(port & 1)) + return _chip[0].read(); + break; + case kDualOpl2: + // Only return for the lower ports + if (port & 1) + return 0xff; + // Make sure the low bits are 6 on opl2 + return _chip[(port >> 1) & 1].read() | 0x6; + } + return 0; +} + +void OPL_DOSBox::writeReg(int r, int v) { + byte tempReg = 0; + switch (_type) { + case kOpl2: + case kDualOpl2: + case kOpl3: + // We can't use _handler->writeReg here directly, since it would miss timer changes. + + // Backup old setup register + tempReg = _reg.normal; + + // We need to set the register we want to write to via port 0x388 + write(0x388, r); + // Do the real writing to the register + write(0x389, v); + // Restore the old register + write(0x388, tempReg); + break; + }; +} + +void OPL_DOSBox::dualWrite(uint8 index, uint8 reg, uint8 val) { + // Make sure you don't use opl3 features + // Don't allow write to disable opl3 + if (reg == 5) + return; + + // Only allow 4 waveforms + if (reg >= 0xE0) + val &= 3; + + // Write to the timer? + if (_chip[index].write(reg, val)) + return; + + // Enabling panning + if (reg >= 0xc0 && reg < 0xc8) { + val &= 7; + val |= index ? 0xA0 : 0x50; + } + + uint32 fullReg = reg + (index ? 0x100 : 0); + _handler->writeReg(fullReg, val); +} + +void OPL_DOSBox::readBuffer(int16 *buffer, int length) { + // For stereo OPL cards, we divide the sample count by 2, + // to match stereo AudioStream behavior. + if (_type != kOpl2) + length >>= 1; + + _handler->generate(buffer, length); +} + +} // end of namespace DOSBox +} // end of namespace OPL + +#endif // !DISABLE_DOSBOX_ADLIB |