From 63141d11132fedfd63860fc42e16de1f48312e01 Mon Sep 17 00:00:00 2001 From: Wohlstand Date: Sat, 11 May 2024 00:00:33 +0300 Subject: Initial implementation of Serial support + refactor of the MIDI play, making separated loop functions instead the mess of everything in one single loop function. --- src/chips/opl_serial_misc.h | 317 ++++++++++++++++++++++++++++++++++++++++++ src/chips/opl_serial_port.cpp | 166 ++++++++++++++++++++++ src/chips/opl_serial_port.h | 65 +++++++++ 3 files changed, 548 insertions(+) create mode 100644 src/chips/opl_serial_misc.h create mode 100644 src/chips/opl_serial_port.cpp create mode 100644 src/chips/opl_serial_port.h (limited to 'src/chips') diff --git a/src/chips/opl_serial_misc.h b/src/chips/opl_serial_misc.h new file mode 100644 index 0000000..84711c3 --- /dev/null +++ b/src/chips/opl_serial_misc.h @@ -0,0 +1,317 @@ +/* + * Interfaces over Yamaha OPL3 (YMF262) chip emulators + * + * Copyright (c) 2017-2023 Vitaly Novichkov (Wohlstand) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef OPL_SERIAL_MISC_H +#define OPL_SERIAL_MISC_H + +#if defined( __unix__) || defined(__APPLE__) +#include +#include +#include +#include +#include +#include +#endif + +#ifdef _WIN32 +#include +#endif + +#include + + +class ChipSerialPortBase +{ +public: + ChipSerialPortBase() {} + virtual ~ChipSerialPortBase() {} + + bool isOpen() + { + return false; + } + + void close() {} + + bool open(const std::string &/*portName*/, unsigned /*baudRate*/) + { + return false; + } + + int write(uint8_t */*data*/, size_t /*size*/) + { + return 0; + } +}; + + + +#if defined( __unix__) || defined(__APPLE__) +class ChipSerialPort : public ChipSerialPortBase +{ + int m_port; + struct termios m_portSetup; + + static unsigned int baud2enum(unsigned int baud) + { + if(baud == 0) + return B0; + else if(baud <= 50) + return B50; + else if(baud <= 75) + return B75; + else if(baud <= 110) + return B110; + else if(baud <= 134) + return B134; + else if(baud <= 150) + return B150; + else if(baud <= 200) + return B200; + else if(baud <= 300) + return B300; + else if(baud <= 600) + return B600; + else if(baud <= 1200) + return B1200; + else if(baud <= 1800) + return B1800; + else if(baud <= 2400) + return B2400; + else if(baud <= 4800) + return B4800; + else if(baud <= 9600) + return B9600; + else if(baud <= 19200) + return B19200; + else if(baud <= 38400) + return B38400; + else if(baud <= 57600) + return B57600; + else if(baud <= 115200) + return B115200; + else + return B230400; + } + +public: + ChipSerialPort() : ChipSerialPortBase() + { + m_port = 0; + std::memset(&m_portSetup, 0, sizeof(struct termios)); + } + + virtual ~ChipSerialPort() + { + close(); + } + + bool isOpen() + { + return m_port != 0; + } + + void close() + { + if(m_port) + ::close(m_port); + + m_port = 0; + } + + bool open(const std::string &portName, unsigned baudRate) + { + if(m_port) + this->close(); + + std::string portPath = "/dev/" + portName; + m_port = ::open(portPath.c_str(), O_WRONLY); + + if(m_port < 0) + { + std::fprintf(stderr, "-- OPL Serial ERROR: failed to open tty device `%s': %s\n", portPath.c_str(), strerror(errno)); + std::fflush(stderr); + m_port = 0; + return false; + } + + if(tcgetattr(m_port, &m_portSetup) != 0) + { + std::fprintf(stderr, "-- OPL Serial ERROR: failed to retrieve setup `%s': %s\n", portPath.c_str(), strerror(errno)); + std::fflush(stderr); + close(); + return false; + } + + m_portSetup.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + m_portSetup.c_oflag &= ~OPOST; + m_portSetup.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + m_portSetup.c_cflag &= ~(CSIZE | PARENB); + m_portSetup.c_cflag |= CS8; + +#if defined (__linux__) || defined (__CYGWIN__) + m_portSetup.c_cflag &= ~CBAUD; +#endif + + cfsetospeed(&m_portSetup, baud2enum(baudRate)); + + if(tcsetattr(m_port, TCSANOW, &m_portSetup) != 0) + { + std::fprintf(stderr, "-- OPL Serial ERROR: failed to apply setup `%s': %s\n", portPath.c_str(), strerror(errno)); + std::fflush(stderr); + close(); + return false; + } + + return true; + } + + int write(uint8_t *data, size_t size) + { + if(!m_port) + return 0; + + return ::write(m_port, data, size); + } +}; + +#endif // __unix__ + + + + +#ifdef _WIN32 + +class ChipSerialPort : public ChipSerialPortBase +{ + HANDLE m_port; + + static unsigned int baud2enum(unsigned int baud) + { + if(baud <= 110) + return CBR_110; + else if(baud <= 300) + return CBR_300; + else if(baud <= 600) + return CBR_600; + else if(baud <= 1200) + return CBR_1200; + else if(baud <= 2400) + return CBR_2400; + else if(baud <= 4800) + return CBR_4800; + else if(baud <= 9600) + return CBR_9600; + else if(baud <= 19200) + return CBR_19200; + else if(baud <= 38400) + return CBR_38400; + else if(baud <= 57600) + return CBR_57600; + else if(baud <= 115200) + return CBR_115200; + else + return CBR_115200; + } + +public: + ChipSerialPort() : ChipSerialPortBase() + { + m_port = NULL; + } + + virtual ~ChipSerialPort() + { + close(); + } + + bool isOpen() + { + return m_port != NULL; + } + + void close() + { + if(m_port) + CloseHandle(m_port); + + m_port = NULL; + } + + bool open(const std::string &portName, unsigned baudRate) + { + if(m_port) + this->close(); + + std::string portPath = "\\\\.\\" + portName; + m_port = CreateFileA(portPath.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + + if(m_port == INVALID_HANDLE_VALUE) + { + m_port = NULL; + return false; + } + + DCB dcb; + BOOL succ; + + SecureZeroMemory(&dcb, sizeof(DCB)); + dcb.DCBlength = sizeof(DCB); + + succ = GetCommState(m_port, &dcb); + if(!succ) + { + this->close(); + return false; + } + + dcb.BaudRate = baud2enum(baudRate); + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + + succ = SetCommState(m_port, &dcb); + + if(!succ) + { + this->close(); + return false; + } + + return true; + } + + int write(uint8_t *data, size_t size) + { + if(!m_port) + return 0; + + DWORD written = 0; + + if(!WriteFile(m_port, data, size, &written, 0)) + return 0; + + return written; + } +}; + +#endif // _WIN32 + +#endif // OPL_SERIAL_MISC_H diff --git a/src/chips/opl_serial_port.cpp b/src/chips/opl_serial_port.cpp new file mode 100644 index 0000000..0d2e58d --- /dev/null +++ b/src/chips/opl_serial_port.cpp @@ -0,0 +1,166 @@ +/* + * Interfaces over Yamaha OPL3 (YMF262) chip emulators + * + * Copyright (c) 2017-2023 Vitaly Novichkov (Wohlstand) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifdef ENABLE_HW_OPL_SERIAL_PORT + +#include "opl_serial_port.h" +#include "opl_serial_misc.h" + + +static size_t retrowave_protocol_serial_pack(const uint8_t *buf_in, size_t len_in, uint8_t *buf_out) +{ + size_t in_cursor = 0; + size_t out_cursor = 0; + + buf_out[out_cursor] = 0x00; + out_cursor += 1; + + uint8_t shift_count = 0; + + while(in_cursor < len_in) + { + uint8_t cur_byte_out = buf_in[in_cursor] >> shift_count; + if(in_cursor > 0) + cur_byte_out |= (buf_in[in_cursor - 1] << (8 - shift_count)); + + cur_byte_out |= 0x01; + buf_out[out_cursor] = cur_byte_out; + + shift_count += 1; + in_cursor += 1; + out_cursor += 1; + if(shift_count > 7) + { + shift_count = 0; + in_cursor -= 1; + } + } + + if(shift_count) + { + buf_out[out_cursor] = buf_in[in_cursor - 1] << (8 - shift_count); + buf_out[out_cursor] |= 0x01; + out_cursor += 1; + } + + buf_out[out_cursor] = 0x02; + out_cursor += 1; + + return out_cursor; +} + +OPL_SerialPort::OPL_SerialPort() + : m_port(NULL), m_protocol(ProtocolUnknown) +{} + +OPL_SerialPort::~OPL_SerialPort() +{ + delete m_port; + m_port = NULL; +} + +bool OPL_SerialPort::connectPort(const std::string& name, unsigned baudRate, unsigned protocol) +{ + delete m_port; + m_port = NULL; + + // ensure audio thread reads protocol atomically and in order, + // so chipType() will be correct after the port is live + m_protocol = protocol; + + // QSerialPort *port = m_port = new QSerialPort(name); + // port->setBaudRate(baudRate); + // return port->open(QSerialPort::WriteOnly); + + m_port = new ChipSerialPort; + return m_port->open(name, baudRate); +} + +void OPL_SerialPort::writeReg(uint16_t addr, uint8_t data) +{ + uint8_t sendBuffer[16]; + ChipSerialPort *port = m_port; + + if(!port || !port->isOpen()) + return; + + switch(m_protocol) + { + default: + case ProtocolArduinoOPL2: + { + if(addr >= 0x100) + break; + sendBuffer[0] = (uint8_t)addr; + sendBuffer[1] = (uint8_t)data; + port->write(sendBuffer, 2); + break; + } + case ProtocolNukeYktOPL3: + { + sendBuffer[0] = (addr >> 6) | 0x80; + sendBuffer[1] = ((addr & 0x3f) << 1) | (data >> 7); + sendBuffer[2] = (data & 0x7f); + port->write(sendBuffer, 3); + break; + } + case ProtocolRetroWaveOPL3: + { + bool port1 = (addr & 0x100) != 0; + uint8_t buf[8] = + { + static_cast(0x21 << 1), 0x12, + static_cast(port1 ? 0xe5 : 0xe1), static_cast(addr & 0xff), + static_cast(port1 ? 0xe7 : 0xe3), static_cast(data), + 0xfb, static_cast(data) + }; + size_t packed_len = retrowave_protocol_serial_pack(buf, sizeof(buf), sendBuffer); + port->write(sendBuffer, packed_len); + break; + } + } +} + +void OPL_SerialPort::nativeGenerate(int16_t *frame) +{ + frame[0] = 0; + frame[1] = 0; +} + +const char *OPL_SerialPort::emulatorName() +{ + return "OPL Serial Port Driver"; +} + +OPLChipBase::ChipType OPL_SerialPort::chipType() +{ + switch(m_protocol) + { + default: + case ProtocolArduinoOPL2: + return OPLChipBase::CHIPTYPE_OPL2; + case ProtocolNukeYktOPL3: + case ProtocolRetroWaveOPL3: + return OPLChipBase::CHIPTYPE_OPL3; + } +} + +#endif // ENABLE_HW_OPL_SERIAL_PORT diff --git a/src/chips/opl_serial_port.h b/src/chips/opl_serial_port.h new file mode 100644 index 0000000..ec7bc39 --- /dev/null +++ b/src/chips/opl_serial_port.h @@ -0,0 +1,65 @@ +/* + * Interfaces over Yamaha OPL3 (YMF262) chip emulators + * + * Copyright (c) 2017-2023 Vitaly Novichkov (Wohlstand) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef OPL_SERIAL_PORT_H +#define OPL_SERIAL_PORT_H + +#ifdef ENABLE_HW_OPL_SERIAL_PORT + +#include +#include "opl_chip_base.h" + +class ChipSerialPort; + +class OPL_SerialPort : public OPLChipBaseT +{ +public: + OPL_SerialPort(); + virtual ~OPL_SerialPort() override; + + enum Protocol + { + ProtocolUnknown, + ProtocolArduinoOPL2, + ProtocolNukeYktOPL3, + ProtocolRetroWaveOPL3 + }; + + bool connectPort(const std::string &name, unsigned baudRate, unsigned protocol); + + bool canRunAtPcmRate() const override { return false; } + void setRate(uint32_t /*rate*/) override {} + void reset() override {} + void writeReg(uint16_t addr, uint8_t data) override; + void nativePreGenerate() override {} + void nativePostGenerate() override {} + void nativeGenerate(int16_t *frame) override; + const char *emulatorName() override; + ChipType chipType() override; + +private: + ChipSerialPort *m_port; + int m_protocol; +}; + +#endif // ENABLE_HW_OPL_SERIAL_PORT + +#endif // OPL_SERIAL_PORT_H -- cgit v1.2.3