scummvm/audio/opl2lpt.cpp
Coen Rampen ca111f3170 AUDIO: Enable dual OPL2 for hardware OPL3
This adds emulation for the dual OPL2 FM synthesis chip configuration to the
supported hardware OPL3 options: RetroWave OPL3 and OPL3LPT. This enables
stereo AdLib playback for SCI (and possibly other engines) on these devices.

This was already implemented for the OPL3 emulators (Dosbox and Nuked) and
ALSA. Generic code has been added to the OPL class to add similar emulation
for the other OPL3 options; this can also be easily added to any future OPL3
hardware or emulators which do not already have dual OPL2 support.
2023-04-27 18:14:24 +02:00

194 lines
4.7 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/* OPL implementation for OPL2LPT and OPL3LPT through libieee1284.
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "common/scummsys.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/str.h"
#include "common/textconsole.h"
#include "audio/fmopl.h"
#include <unistd.h>
#include <ieee1284.h>
static const uint8 OPL2LPTRegisterSelect[] = {
(C1284_NSELECTIN | C1284_NSTROBE | C1284_NINIT) ^ C1284_INVERTED,
(C1284_NSELECTIN | C1284_NSTROBE) ^ C1284_INVERTED,
(C1284_NSELECTIN | C1284_NSTROBE | C1284_NINIT) ^ C1284_INVERTED
};
static const uint8 OPL3LPTRegisterSelect[] = {
(C1284_NSTROBE | C1284_NINIT) ^ C1284_INVERTED,
C1284_NSTROBE ^ C1284_INVERTED,
(C1284_NSTROBE | C1284_NINIT) ^ C1284_INVERTED
};
static const uint8 OPL2LPTRegisterWrite[] = {
(C1284_NSELECTIN | C1284_NINIT) ^ C1284_INVERTED,
C1284_NSELECTIN ^ C1284_INVERTED,
(C1284_NSELECTIN | C1284_NINIT) ^ C1284_INVERTED
};
namespace OPL {
namespace OPL2LPT {
class OPL : public ::OPL::RealOPL {
private:
struct parport *_pport;
Config::OplType _type;
int index;
public:
explicit OPL(Config::OplType type);
~OPL();
bool init();
void reset();
void write(int a, int v);
byte read(int a);
void writeReg(int r, int v);
};
OPL::OPL(Config::OplType type) : _pport(nullptr), _type(type), index(0) {
}
OPL::~OPL() {
if (_pport) {
stop();
reset();
ieee1284_close(_pport);
}
}
bool OPL::init() {
struct parport_list parports = {};
const Common::String parportName = ConfMan.get("opl2lpt_parport");
// Look for available parallel ports
if (ieee1284_find_ports(&parports, 0) != E1284_OK) {
return false;
}
for (int i = 0; i < parports.portc; i++) {
if (parportName == "null" ||
parportName == parports.portv[i]->name) {
int caps = CAP1284_RAW;
_pport = parports.portv[i];
if (ieee1284_open(_pport, F1284_EXCL, &caps) != E1284_OK) {
warning("cannot open parallel port %s", _pport->name);
}
if (ieee1284_claim(_pport) != E1284_OK) {
warning("cannot claim parallel port %s", _pport->name);
ieee1284_close(_pport);
continue;
}
reset();
// Safe to free ports here, opened ports are refcounted.
ieee1284_free_ports(&parports);
return true;
}
}
_pport = nullptr;
ieee1284_free_ports(&parports);
return false;
}
void OPL::reset() {
for(int i = 0; i < 256; i ++) {
writeReg(i, 0);
}
if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
for (int i = 0; i < 256; i++) {
writeReg(i + 256, 0);
}
}
index = 0;
initDualOpl2OnOpl3(_type);
}
void OPL::write(int port, int val) {
if (port & 1) {
writeReg(index, val);
} else {
switch (_type) {
case Config::kOpl2:
index = val & 0xff;
break;
case Config::kOpl3:
case Config::kDualOpl2:
index = (val & 0xff) | ((port << 7) & 0x100);
break;
default:
warning("OPL2LPT: unsupported OPL mode %d", _type);
break;
}
}
}
byte OPL::read(int port) {
// No read support for the OPL2LPT
return 0;
}
void OPL::writeReg(int r, int v) {
if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
r &= 0x1ff;
} else {
r &= 0xff;
}
v &= 0xff;
if (!emulateDualOpl2OnOpl3(r, v, _type))
return;
ieee1284_write_data(_pport, r & 0xff);
if (r < 0x100) {
ieee1284_write_control(_pport, OPL2LPTRegisterSelect[0]);
ieee1284_write_control(_pport, OPL2LPTRegisterSelect[1]);
ieee1284_write_control(_pport, OPL2LPTRegisterSelect[2]);
} else {
ieee1284_write_control(_pport, OPL3LPTRegisterSelect[0]);
ieee1284_write_control(_pport, OPL3LPTRegisterSelect[1]);
ieee1284_write_control(_pport, OPL3LPTRegisterSelect[2]);
}
usleep(4); // 3.3 us
ieee1284_write_data(_pport, v);
ieee1284_write_control(_pport, OPL2LPTRegisterWrite[0]);
ieee1284_write_control(_pport, OPL2LPTRegisterWrite[1]);
ieee1284_write_control(_pport, OPL2LPTRegisterWrite[2]);
usleep(23);
}
OPL *create(Config::OplType type) {
return new OPL(type);
}
} // End of namespace OPL2LPT
} // End of namespace OPL