From 4c56a6c1b2773cef2246e835b40c2ecc3e2bbce6 Mon Sep 17 00:00:00 2001 From: Stian Skjelstad Date: Mon, 26 Jun 2023 23:50:11 +0200 Subject: [PATCH 1/3] Add support for SudoMaker RetroWave OPL3 express --- configure.ac | 24 ++-- src/Makefile.am | 1 + src/adplay.cc | 20 +++ src/players.h | 8 +- src/retrowave.cc | 346 +++++++++++++++++++++++++++++++++++++++++++++++ src/retrowave.h | 69 ++++++++++ 6 files changed, 459 insertions(+), 9 deletions(-) create mode 100644 src/retrowave.cc create mode 100644 src/retrowave.h diff --git a/configure.ac b/configure.ac index 65ef204..83b2d1b 100644 --- a/configure.ac +++ b/configure.ac @@ -96,6 +96,7 @@ AC_ARG_ENABLE([output-oss],AS_HELP_STRING([--disable-output-oss],[Disable OSS ou AC_ARG_ENABLE([output-null],AS_HELP_STRING([--disable-output-null],[Disable null output])) AC_ARG_ENABLE([output-disk],AS_HELP_STRING([--disable-output-disk],[Disable disk writer])) AC_ARG_ENABLE([output-esound],AS_HELP_STRING([--disable-output-esound],[Disable EsounD output])) +AC_ARG_ENABLE([output-retrowave],AS_HELP_STRING([--disable-output-retrowave],[Disable RetroWave output])) AC_ARG_ENABLE([output-qsa],AS_HELP_STRING([--disable-output-qsa],[Disable QSA output])) AC_ARG_ENABLE([output-sdl],AS_HELP_STRING([--disable-output-sdl],[Disable SDL output])) AC_ARG_ENABLE([output-alsa],AS_HELP_STRING([--disable-output-alsa],[Disable ALSA output])) @@ -146,6 +147,12 @@ if test ${enable_output_esound:=yes} = yes; then AC_MSG_RESULT([*** EsounD (libesd) >= 0.2.8 not installed ***])) fi +# RetroWave output +if test ${enable_output_retrowave:=yes} = yes; then + AC_DEFINE(DRIVER_RETROWAVE,1,[Build RetroWave output]) + drivers=$drivers' retrowave.$(OBJEXT)' +fi + # QSA driver if test ${enable_output_qsa:=yes} = yes; then AC_MSG_CHECKING([for QSA headers]) @@ -219,11 +226,12 @@ echo "Configuration:" echo "Install path: ${prefix}" echo "" echo "Build output mechanisms:" -echo "OSS output (oss): ${enable_output_oss}" -echo "Null output (null): ${enable_output_null}" -echo "Disk writer (disk): ${enable_output_disk}" -echo "EsounD output (esound): ${enable_output_esound}" -echo "QSA output (qsa): ${enable_output_qsa}" -echo "SDL output (sdl): ${enable_output_sdl}" -echo "ALSA output (alsa): ${enable_output_alsa}" -echo "Libao output (ao): ${enable_output_ao}" +echo "OSS output (oss): ${enable_output_oss}" +echo "Null output (null): ${enable_output_null}" +echo "Disk writer (disk): ${enable_output_disk}" +echo "EsounD output (esound): ${enable_output_esound}" +echo "Retrowave output (retrowave) ${enable_output_retrowave}" +echo "QSA output (qsa): ${enable_output_qsa}" +echo "SDL output (sdl): ${enable_output_sdl}" +echo "ALSA output (alsa): ${enable_output_alsa}" +echo "Libao output (ao): ${enable_output_ao}" diff --git a/src/Makefile.am b/src/Makefile.am index bf4f884..26042ef 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,6 +4,7 @@ adplay_SOURCES = adplay.cc output.cc output.h players.h defines.h EXTRA_adplay_SOURCES = oss.cc oss.h null.h disk.cc disk.h esound.cc esound.h \ qsa.cc qsa.h sdl.cc sdl.h alsa.cc alsa.h ao.cc ao.h getopt.c \ + retrowave.cc retrowave.h \ getopt1.c getopt.h adplay_LDADD = $(drivers) $(adplug_LIBS) @ESD_LIBS@ @QSA_LIBS@ @SDL_LIBS@ \ diff --git a/src/adplay.cc b/src/adplay.cc index 71977c9..e76d62e 100644 --- a/src/adplay.cc +++ b/src/adplay.cc @@ -151,6 +151,10 @@ static void usage() "EsounD driver (esound) specific:\n" " -d, --device=URL URL to EsounD server host (hostname:port)\n\n" #endif +#ifdef DRIVER_RETROWAVE + "RetroWave driver (retrowave) specific:\n" + " -d --device=DEVICE set sound device to DEVICE\n\n" +#endif #ifdef DRIVER_SDL "SDL driver (sdl) specific:\n" " -b, --buffer=SIZE set output buffer size to SIZE\n\n" @@ -202,6 +206,9 @@ static void usage() #ifdef DRIVER_ESOUND "esound " #endif +#ifdef DRIVER_RETROWAVE + "retrowave " +#endif #ifdef DRIVER_QSA "qsa " #endif @@ -300,6 +307,10 @@ static int decode_switches(int argc, char **argv) if(!strcmp(optarg,"alsa")) cfg.output = alsa; else #endif +#ifdef DRIVER_RETROWAVE + if(!strcmp(optarg,"retrowave")) cfg.output = retrowave; + else +#endif #ifdef DRIVER_SDL if(!strcmp(optarg,"sdl")) cfg.output = sdl; else @@ -579,6 +590,15 @@ int main(int argc, char **argv) player = new AOPlayer(opl, cfg.device, cfg.bits, cfg.channels, cfg.freq, cfg.buf_size); break; #endif +#ifdef DRIVER_RETROWAVE + case retrowave: + if (opl) { + delete(opl); + opl = 0; + } + player = new RetroWavePlayer(cfg.device); + break; +#endif #ifdef DRIVER_SDL case sdl: player = new SDLPlayer(opl, cfg.bits, cfg.channels, cfg.freq, cfg.buf_size); diff --git a/src/players.h b/src/players.h index e5d812b..dc31966 100644 --- a/src/players.h +++ b/src/players.h @@ -28,7 +28,7 @@ #include "config.h" // Enumerate ALL outputs (regardless of availability) -enum Outputs {none, null, ao, oss, disk, esound, qsa, sdl, alsa}; +enum Outputs {none, null, ao, oss, disk, esound, retrowave, qsa, sdl, alsa}; #define DEFAULT_DRIVER none @@ -39,6 +39,12 @@ enum Outputs {none, null, ao, oss, disk, esound, qsa, sdl, alsa}; #define DEFAULT_DRIVER null #endif +#ifdef DRIVER_RETROWAVE +#include "retrowave.h" +#undef DEFAULT_DRIVER +#define DEFAULT_DRIVER retrowave +#endif + // Disk writer #ifdef DRIVER_DISK #include "disk.h" diff --git a/src/retrowave.cc b/src/retrowave.cc new file mode 100644 index 0000000..1814fa4 --- /dev/null +++ b/src/retrowave.cc @@ -0,0 +1,346 @@ +/* + * AdPlay/UNIX - OPL2 audio player + * Copyright (C) 2023 Stian Skjelstad + * + * Code from https://github.com/SudoMaker/RetroWave is used as reference + * + * 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "defines.h" +#include "retrowave.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef __APPLE__ +#include +#endif + +#if defined (__CYGWIN__) +#define RETROWAVE_PLAYER_TIME_REF CLOCK_REALTIME +#elif defined (__linux) +#define RETROWAVE_PLAYER_TIME_REF CLOCK_MONOTONIC_RAW +#else +#define RETROWAVE_PLAYER_TIME_REF CLOCK_MONOTONIC +#endif + + +typedef enum { + RetroWave_Board_Unknown = 0, + RetroWave_Board_OPL3 = 0x21 << 1, + RetroWave_Board_MiniBlaster = 0x20 << 1, + RetroWave_Board_MasterGear = 0x24 << 1 +} RetroWaveBoardType; + +RetroWaveOpl::RetroWaveOpl(const char *filename) +{ + struct termios tio; + currType = TYPE_OPL3; + fd = open(filename, O_RDWR); + if (fd < 0) + { + fprintf(stderr, "Failed to open tty/serial device %s: %s\n", filename, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (flock(fd, LOCK_EX)) + { + fprintf(stderr, "Failed to lock tty/serial device: %s: %s\n", filename, strerror(errno)); + close(fd); + fd = -1; + exit(EXIT_FAILURE); + } + + if (tcgetattr(fd, &tio)) + { + fprintf(stderr, "Failed to perform tcgetattr() on device %s, not a tty/serial device?: %s\n", filename, strerror (errno)); + close(fd); + fd = -1; + exit(EXIT_FAILURE); + } + cfmakeraw(&tio); + +#ifndef __APPLE__ + cfsetispeed(&tio, 2000000); + cfsetospeed(&tio, 2000000); +#endif + + if (tcgetattr(fd, &tio)) + { + fprintf(stderr, "Failed to perform tcsetattr() on device %s, not a tty/serial device?: %s\n", filename, strerror(errno)); + close(fd); + fd = -1; + exit(EXIT_FAILURE); + } + +#ifdef __APPLE__ + int speed = 2000000; + + if (ioctl(fd, IOSSIOSPEED, &speed) == -1) + { + fprintf(stderr, "Failed to set baudrate on device %s: %s", filename, strerror(errno)); + exit(EXIT_FAILURE); + } +#endif + + cmd_buffer[0] = 0x00; + cmd_buffer_used=1; + io_prepare(); + flush(); + + for (uint8_t i=0x20; i<0x28; i++) + { + cmd_prepare((uint8_t)(i<<1), 0x0a, 1); // IOCON register + cmd_buffer[cmd_buffer_used++] = 0x28; // Enable: HAEN, SEQOP + io_prepare(); + flush(); + + cmd_prepare((uint8_t)(i<<1), 0x00, 2); // IODIRA register + cmd_buffer[cmd_buffer_used++] = 0x00; // Set output + cmd_buffer[cmd_buffer_used++] = 0x00; // Set output + io_prepare(); + flush(); + + cmd_prepare((uint8_t)(i<<1), 0x12, 2); // GPIOA register + cmd_buffer[cmd_buffer_used++] = 0xff; // Set all HIGH + cmd_buffer[cmd_buffer_used++] = 0xff; // Set all HIGH + cmd_buffer_used = 4; + io_prepare(); + flush(); + } +} + +RetroWaveOpl::~RetroWaveOpl() +{ + if (fd >= 0) + { + close(fd); + fd = -1; + } +} + +void RetroWaveOpl::cmd_prepare(uint8_t io_addr, uint8_t io_reg, const int len) +{ + if ((cmd_buffer_used > (8192-len))|| + (cmd_buffer_used && (cmd_buffer[0] != io_addr)) || + (cmd_buffer_used && (cmd_buffer[1] != io_reg)) ) + { + io_prepare(); + flush(); + } + + if (!cmd_buffer_used) + { + cmd_buffer[cmd_buffer_used++] = io_addr; + cmd_buffer[cmd_buffer_used++] = io_reg; + } +} + +void RetroWaveOpl::queue_port0(uint8_t reg, uint8_t val) +{ + cmd_prepare(RetroWave_Board_OPL3, 0x12, 6); + cmd_buffer[cmd_buffer_used++] = 0xe1; + cmd_buffer[cmd_buffer_used++] = reg; + cmd_buffer[cmd_buffer_used++] = 0xe3; + cmd_buffer[cmd_buffer_used++] = val; + cmd_buffer[cmd_buffer_used++] = 0xfb; + cmd_buffer[cmd_buffer_used++] = val; +} + +void RetroWaveOpl::queue_port1(uint8_t reg, uint8_t val) +{ + cmd_prepare(RetroWave_Board_OPL3, 0x12, 6); + cmd_buffer[cmd_buffer_used++] = 0xe5; + cmd_buffer[cmd_buffer_used++] = reg; + cmd_buffer[cmd_buffer_used++] = 0xe7; + cmd_buffer[cmd_buffer_used++] = val; + cmd_buffer[cmd_buffer_used++] = 0xfb; + cmd_buffer[cmd_buffer_used++] = val; +} + +void RetroWaveOpl::reset(void) +{ + if (cmd_buffer_used) + { + io_prepare(); + flush(); + } + + cmd_prepare(RetroWave_Board_OPL3, 0x12, 1); + cmd_buffer[cmd_buffer_used++] = 0xfe; + io_prepare(); + flush(); + + cmd_prepare(RetroWave_Board_OPL3, 0x12, 1); + cmd_buffer[cmd_buffer_used++] = 0xff; + io_prepare(); + flush(); + + queue_port1 (5, 1); + queue_port1 (4, 0); + + for (int i=0x20; i < 0x35; i++) + { + queue_port0 (i, 0); + queue_port1 (i, 0); + } + for (int i=0xa0; i < 0xa8; i++) + { + queue_port0 (i, 0); + queue_port1 (i, 0); + } + for (int i=0xb0; i < 0xb8; i++) + { + queue_port0 (i, 0); + queue_port1 (i, 0); + } + for (int i=0xbd; i < 0xbd; i++) + { + queue_port0 (i, 0); + queue_port1 (i, 0); + } + for (int i=0xc0; i < 0xc8; i++) + { + queue_port0 (i, 0x30); + queue_port1 (i, 0x30); + } + for (int i=0xe0; i < 0xf5; i++) + { + queue_port0 (i, 0); + queue_port1 (i, 0); + } + for (int i=0x08; i < 0x08; i++) + { + queue_port0 (i, 0); + queue_port1 (i, 0); + } + for (int i=0x01; i < 0x01; i++) + { + queue_port0 (i, 0); + queue_port1 (i, 0); + } + queue_port1 (5, 0); + io_prepare(); + flush(); +} + +void RetroWaveOpl::io_prepare(void) +{ + uint_fast16_t data = 0; + uint8_t fill = 0; + io_buffer_used = 0; + io_buffer[io_buffer_used++] = 0x00; + + if (!cmd_buffer_used) + { + return; + } + + for (uint_fast16_t i=0; i < cmd_buffer_used;) + { + if (fill < 7) + { + data <<= 8; + data |= cmd_buffer[i++]; + fill += 8; + } + io_buffer[io_buffer_used++] = ((data >> (fill - 7)) << 1) | 0x01; + fill -= 7; + } + if (fill) + { + io_buffer[io_buffer_used++] = 0x01 | (data << 1); + } + + io_buffer[io_buffer_used++] = 0x02; + + cmd_buffer_used = 0; +} + +void RetroWaveOpl::flush(void) +{ + if (!io_buffer_used) + { + return; + } + ::write(fd, io_buffer, io_buffer_used); + io_buffer_used = 0; +} + +void RetroWaveOpl::write(int reg, int val) +{ + if (currChip == 0) + { + queue_port0 (reg, val); + } else if (currChip == 1) + { + queue_port1 (reg, val); + } +} +void RetroWaveOpl::init(void) +{ + reset(); + currChip = 0; +} + + +RetroWavePlayer::RetroWavePlayer(const char *filename) : EmuPlayer(0, 16, 2, 44100, 65536) +{ + retrowaveopl = new RetroWaveOpl(filename ? filename : "/dev/ttyACM0"); + clock_gettime(RETROWAVE_PLAYER_TIME_REF, &nexttick); +} + +RetroWavePlayer::~RetroWavePlayer() +{ + delete retrowaveopl; + retrowaveopl = NULL; +} + +Copl *RetroWavePlayer::get_opl(void) +{ + return retrowaveopl; +} + +void RetroWavePlayer::frame() +{ + playing = p->update(); + retrowaveopl->io_prepare(); + retrowaveopl->flush(); + long long ticklen = 1000000000ll / p->getrefresh(); + struct timespec now; + clock_gettime(RETROWAVE_PLAYER_TIME_REF, &now); + nexttick.tv_nsec += ticklen; + while (nexttick.tv_nsec > 1000000000) + { + nexttick.tv_sec++; + nexttick.tv_nsec-= 1000000000; + } + if (nexttick.tv_sec < now.tv_sec) + { + return; + } + if ((nexttick.tv_sec == now.tv_sec) && + (nexttick.tv_nsec < now.tv_nsec)) + { + return; + } + usleep( (nexttick.tv_sec - now.tv_sec ) * 1000000 + + (nexttick.tv_nsec - now.tv_nsec) / 1000); +} diff --git a/src/retrowave.h b/src/retrowave.h new file mode 100644 index 0000000..5fd16f4 --- /dev/null +++ b/src/retrowave.h @@ -0,0 +1,69 @@ +/* + * AdPlay/UNIX - OPL2 audio player + * Copyright (C) 2023 Stian Skjelstad + * + * 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef H_RETROWAVE +#define H_RETROWAVE + +#include +#include "output.h" + +class RetroWaveOpl : public Copl +{ +public: + RetroWaveOpl(const char *filename); + ~RetroWaveOpl(); + + void io_prepare(void); + void flush(void); + +protected: + void queue_port0(uint8_t reg, uint8_t val); + void queue_port1(uint8_t reg, uint8_t val); + void reset(void); + +private: + int fd = -1; + void cmd_prepare(uint8_t io_addr, uint8_t io_reg, const int len); + uint8_t cmd_buffer[8192]; + uint_fast16_t cmd_buffer_used = 0; + uint8_t io_buffer[9365]; + uint_fast16_t io_buffer_used; + + void write(int reg, int val); + void init(void); +}; + +class RetroWavePlayer : public EmuPlayer +{ +public: + RetroWavePlayer(const char *filename); + ~RetroWavePlayer(); + + void frame(); + Copl *get_opl(void); + +protected: + void output(const void *buf, unsigned long size) {}; + +private: + struct timespec nexttick; + RetroWaveOpl *retrowaveopl; +}; + +#endif From 3f1f671224aad3c255b9d5642c8fe93b76753c53 Mon Sep 17 00:00:00 2001 From: Stian Skjelstad Date: Wed, 28 Jun 2023 08:06:14 +0200 Subject: [PATCH 2/3] Reset RetroWave OPL3 upon unscheduled exit --- src/retrowave.cc | 35 ++++++++++++++++++++++++++++++++++- src/retrowave.h | 2 +- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/retrowave.cc b/src/retrowave.cc index 1814fa4..f06356f 100644 --- a/src/retrowave.cc +++ b/src/retrowave.cc @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -41,7 +42,6 @@ #define RETROWAVE_PLAYER_TIME_REF CLOCK_MONOTONIC #endif - typedef enum { RetroWave_Board_Unknown = 0, RetroWave_Board_OPL3 = 0x21 << 1, @@ -49,6 +49,23 @@ typedef enum { RetroWave_Board_MasterGear = 0x24 << 1 } RetroWaveBoardType; +static RetroWaveOpl *instance; + +static void retrowave_sighandler (int ignore) +{ + static int recursive = 0; + + if (!recursive) + { + recursive = 1; + if (instance) + { + instance->reset(); + } + _exit(0); + } +} + RetroWaveOpl::RetroWaveOpl(const char *filename) { struct termios tio; @@ -304,12 +321,28 @@ void RetroWaveOpl::init(void) RetroWavePlayer::RetroWavePlayer(const char *filename) : EmuPlayer(0, 16, 2, 44100, 65536) { retrowaveopl = new RetroWaveOpl(filename ? filename : "/dev/ttyACM0"); + + instance = retrowaveopl; +#ifdef SIGHUP + signal (SIGHUP, retrowave_sighandler); +#endif +#ifdef SIGQUIT + signal (SIGQUIT, retrowave_sighandler); +#endif +#ifdef SIGINT + signal (SIGINT, retrowave_sighandler); +#endif +#ifdef SIGKILL + signal (SIGKILL, retrowave_sighandler); +#endif + clock_gettime(RETROWAVE_PLAYER_TIME_REF, &nexttick); } RetroWavePlayer::~RetroWavePlayer() { delete retrowaveopl; + instance = NULL; retrowaveopl = NULL; } diff --git a/src/retrowave.h b/src/retrowave.h index 5fd16f4..3d3270d 100644 --- a/src/retrowave.h +++ b/src/retrowave.h @@ -31,11 +31,11 @@ class RetroWaveOpl : public Copl void io_prepare(void); void flush(void); + void reset(void); protected: void queue_port0(uint8_t reg, uint8_t val); void queue_port1(uint8_t reg, uint8_t val); - void reset(void); private: int fd = -1; From 73f9ee813d592b68df543cc0aaaa43c34715de78 Mon Sep 17 00:00:00 2001 From: Stian Skjelstad Date: Sun, 16 Jul 2023 10:39:09 +0200 Subject: [PATCH 3/3] * Setting Baud-Rate appears to not be needed, and does not compile correctly an multiple systems * Reset by pin did not work due to firmware bug when only sending one byte of data, but it makes a clicking sound so we prefer to just set the OPL3 into a known state * OS X have bug in the USB serial buffering, so we must send smaller chunks at a time --- src/retrowave.cc | 104 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 28 deletions(-) diff --git a/src/retrowave.cc b/src/retrowave.cc index f06356f..28954a4 100644 --- a/src/retrowave.cc +++ b/src/retrowave.cc @@ -94,11 +94,12 @@ RetroWaveOpl::RetroWaveOpl(const char *filename) } cfmakeraw(&tio); +#if 0 /* setting the speed appears to not be needed, and 2000000 is not valid on all operating systems */ #ifndef __APPLE__ cfsetispeed(&tio, 2000000); cfsetospeed(&tio, 2000000); #endif - +#endif if (tcgetattr(fd, &tio)) { fprintf(stderr, "Failed to perform tcsetattr() on device %s, not a tty/serial device?: %s\n", filename, strerror(errno)); @@ -107,6 +108,7 @@ RetroWaveOpl::RetroWaveOpl(const char *filename) exit(EXIT_FAILURE); } +#if 0 /* setting the speed appears to not be needed, and 2000000 is not valid on all operating systems */ #ifdef __APPLE__ int speed = 2000000; @@ -115,6 +117,7 @@ RetroWaveOpl::RetroWaveOpl(const char *filename) fprintf(stderr, "Failed to set baudrate on device %s: %s", filename, strerror(errno)); exit(EXIT_FAILURE); } +#endif #endif cmd_buffer[0] = 0x00; @@ -122,10 +125,21 @@ RetroWaveOpl::RetroWaveOpl(const char *filename) io_prepare(); flush(); + /* GPIOA.0 = /IC Initial clear (Reset) + * GPIOA.1 = A0 Low=Address, High=Data + * GPIOA.2 = A1 Low=Bank0, High=Bank1 + * GPIOA.3 = /WR Write enable + * GPIOA.4 = /CS Chip Select + * GPIOA.5 = + * GPIOA.6 = + * GPIOA.7 = + * GPIOB[0:7] = D[0:7] + */ + for (uint8_t i=0x20; i<0x28; i++) { cmd_prepare((uint8_t)(i<<1), 0x0a, 1); // IOCON register - cmd_buffer[cmd_buffer_used++] = 0x28; // Enable: HAEN, SEQOP + cmd_buffer[cmd_buffer_used++] = 0x28; // HAEN=1 SEQOP=1 BANK=0 io_prepare(); flush(); @@ -172,24 +186,24 @@ void RetroWaveOpl::cmd_prepare(uint8_t io_addr, uint8_t io_reg, const int len) void RetroWaveOpl::queue_port0(uint8_t reg, uint8_t val) { - cmd_prepare(RetroWave_Board_OPL3, 0x12, 6); + cmd_prepare(RetroWave_Board_OPL3, 0x12, 6); // GPIOA register cmd_buffer[cmd_buffer_used++] = 0xe1; cmd_buffer[cmd_buffer_used++] = reg; cmd_buffer[cmd_buffer_used++] = 0xe3; cmd_buffer[cmd_buffer_used++] = val; cmd_buffer[cmd_buffer_used++] = 0xfb; - cmd_buffer[cmd_buffer_used++] = val; + cmd_buffer[cmd_buffer_used++] = val; // Retrowave express OPL3 seems to only like even data writes } void RetroWaveOpl::queue_port1(uint8_t reg, uint8_t val) { - cmd_prepare(RetroWave_Board_OPL3, 0x12, 6); + cmd_prepare(RetroWave_Board_OPL3, 0x12, 6); // GPIOA register cmd_buffer[cmd_buffer_used++] = 0xe5; cmd_buffer[cmd_buffer_used++] = reg; cmd_buffer[cmd_buffer_used++] = 0xe7; cmd_buffer[cmd_buffer_used++] = val; cmd_buffer[cmd_buffer_used++] = 0xfb; - cmd_buffer[cmd_buffer_used++] = val; + cmd_buffer[cmd_buffer_used++] = val; // Retrowave express OPL3 seems to only like even data writes } void RetroWaveOpl::reset(void) @@ -200,62 +214,83 @@ void RetroWaveOpl::reset(void) flush(); } - cmd_prepare(RetroWave_Board_OPL3, 0x12, 1); +#if 0 // reset by /IC, makes click sound + cmd_prepare(RetroWave_Board_OPL3, 0x12, 1); // GPIOA register cmd_buffer[cmd_buffer_used++] = 0xfe; + cmd_buffer[cmd_buffer_used++] = 0x00; // Retrowave express OPL3 seems to only like even data writes io_prepare(); flush(); - cmd_prepare(RetroWave_Board_OPL3, 0x12, 1); + usleep (1700); /* chip needs about 1.6ms to safely reset, so delay 1.7ms */ + + cmd_prepare(RetroWave_Board_OPL3, 0x12, 1); // GPIOA register cmd_buffer[cmd_buffer_used++] = 0xff; + cmd_buffer[cmd_buffer_used++] = 0x00; // Retrowave express OPL3 seems to only like even data writes io_prepare(); flush(); +#else // reset by registers + queue_port1 (5, 1); // Enable OPL3 mode + queue_port1 (4, 0); // Disable all 4-OP connections - queue_port1 (5, 1); - queue_port1 (4, 0); - - for (int i=0x20; i < 0x35; i++) + for (int i=0x20; i <= 0x35; i++) { - queue_port0 (i, 0); - queue_port1 (i, 0); + queue_port0 (i, 0x01); + queue_port1 (i, 0x01); } - for (int i=0xa0; i < 0xa8; i++) + for (int i=0x40; i <= 0x55; i++) { - queue_port0 (i, 0); - queue_port1 (i, 0); + queue_port0 (i, 0x3f); + queue_port1 (i, 0x3f); } - for (int i=0xb0; i < 0xb8; i++) + for (int i=0x60; i <= 0x75; i++) { - queue_port0 (i, 0); - queue_port1 (i, 0); + queue_port0 (i, 0xee); + queue_port1 (i, 0xee); + } + for (int i=0x80; i <= 0x95; i++) + { + queue_port0 (i, 0x0e); + queue_port1 (i, 0x0e); + } + for (int i=0xa0; i <= 0xa8; i++) + { + queue_port0 (i, 0x80); + queue_port1 (i, 0x80); } - for (int i=0xbd; i < 0xbd; i++) + for (int i=0xb0; i <= 0xb8; i++) + { + queue_port0 (i, 0x04); + queue_port1 (i, 0x04); + } + for (int i=0xbd; i <= 0xbd; i++) { queue_port0 (i, 0); queue_port1 (i, 0); } - for (int i=0xc0; i < 0xc8; i++) + for (int i=0xc0; i <= 0xc8; i++) { - queue_port0 (i, 0x30); - queue_port1 (i, 0x30); + queue_port0 (i, 0x30); // Enable Left and Right + queue_port1 (i, 0x30); // Enable Left and Right } - for (int i=0xe0; i < 0xf5; i++) + for (int i=0xe0; i <= 0xf5; i++) { queue_port0 (i, 0); queue_port1 (i, 0); } - for (int i=0x08; i < 0x08; i++) + for (int i=0x08; i <= 0x08; i++) { queue_port0 (i, 0); queue_port1 (i, 0); } - for (int i=0x01; i < 0x01; i++) + for (int i=0x01; i <= 0x01; i++) { queue_port0 (i, 0); queue_port1 (i, 0); } - queue_port1 (5, 0); + queue_port1 (5, 0); // OPL2 mode io_prepare(); flush(); +#endif } void RetroWaveOpl::io_prepare(void) @@ -297,7 +332,20 @@ void RetroWaveOpl::flush(void) { return; } +#if defined(__APPLE__) /* Atleast OS X El Capitan has issues with buffering causing corruption*/ + int pos = 0; + while (pos < io_buffer_used) + { + int target = io_buffer_used - pos; + if (target > 128) target = 128; + int res; + ::write(fd, io_buffer + pos, target); + pos += target; + usleep (250); + } +#else ::write(fd, io_buffer, io_buffer_used); +#endif io_buffer_used = 0; }