aboutsummaryrefslogtreecommitdiff
path: root/sound/softsynth/opl/dosbox.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sound/softsynth/opl/dosbox.cpp')
-rw-r--r--sound/softsynth/opl/dosbox.cpp355
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