diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/adlmidi.cpp | 70 | ||||
-rw-r--r-- | src/adlmidi_load.cpp | 2 | ||||
-rw-r--r-- | src/adlmidi_midiplay.cpp | 30 | ||||
-rw-r--r-- | src/adlmidi_midiplay.hpp | 9 | ||||
-rw-r--r-- | src/adlmidi_opl3.cpp | 71 | ||||
-rw-r--r-- | src/adlmidi_opl3.hpp | 23 | ||||
-rw-r--r-- | src/adlmidi_sequencer.cpp | 4 | ||||
-rw-r--r-- | src/chips/opl_serial_misc.h | 317 | ||||
-rw-r--r-- | src/chips/opl_serial_port.cpp | 166 | ||||
-rw-r--r-- | src/chips/opl_serial_port.h | 65 |
10 files changed, 738 insertions, 19 deletions
diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index be4b6bf..a642259 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -131,13 +131,20 @@ ADLMIDI_EXPORT int adl_setNumChips(ADL_MIDIPlayer *device, int numChips) MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); + +#ifdef ADLMIDI_ENABLE_HW_SERIAL + if(play->m_setup.serial) + numChips = 1; +#endif + #ifdef ADLMIDI_HW_OPL ADL_UNUSED(numChips); play->m_setup.numChips = 1; #else play->m_setup.numChips = static_cast<unsigned int>(numChips); #endif - if(play->m_setup.numChips < 1 || play->m_setup.numChips > ADL_MAX_CHIPS) + + if((play->m_setup.numChips < 1) || (play->m_setup.numChips > ADL_MAX_CHIPS)) { play->setErrorString("number of chips may only be 1.." ADL_MAX_CHIPS_STR ".\n"); return -1; @@ -618,6 +625,10 @@ ADLMIDI_EXPORT void adl_setSoftPanEnabled(ADL_MIDIPlayer *device, int softPanEn) MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_synth->m_softPanning = (softPanEn != 0); +#ifdef ADLMIDI_ENABLE_HW_SERIAL + if(play->m_setup.serial) // Soft-panning won't work while serial is active + play->m_synth->m_softPanning = false; +#endif } /* !!!DEPRECATED!!! */ @@ -846,6 +857,9 @@ ADLMIDI_EXPORT int adl_switchEmulator(struct ADL_MIDIPlayer *device, int emulato if(adl_isEmulatorAvailable(emulator)) { play->m_setup.emulator = emulator; +#ifdef ADLMIDI_ENABLE_HW_SERIAL + play->m_setup.serial = false; +#endif play->partialReset(); return 0; } @@ -870,6 +884,31 @@ ADLMIDI_EXPORT int adl_setRunAtPcmRate(ADL_MIDIPlayer *device, int enabled) return -1; } +ADLMIDI_EXPORT int adl_switchSerialHW(struct ADL_MIDIPlayer *device, + const char *name, + unsigned baud, + unsigned protocol) +{ + if(device) + { + MidiPlayer *play = GET_MIDI_PLAYER(device); + assert(play); +#ifdef ADLMIDI_ENABLE_HW_SERIAL + play->m_setup.serial = true; + play->m_setup.serialName = std::string(name); + play->m_setup.serialBaud = baud; + play->m_setup.serialProtocol = protocol; + play->partialReset(); + return 0; +#else + (void)name; (void)baud; (void)protocol; + play->setErrorString("ADLMIDI: The hardware serial mode is not enabled in this build"); + return -1; +#endif + } + + return -1; +} ADLMIDI_EXPORT const char *adl_linkedLibraryVersion() { @@ -1498,7 +1537,10 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, hasSkipped = setup.tick_skip_samples_delay > 0; } else + { setup.delay = player->Tick(eat_delay, setup.mindelay); + player->TickIterators(eat_delay); + } } return static_cast<int>(gotten_len); @@ -1593,6 +1635,24 @@ ADLMIDI_EXPORT double adl_tickEvents(struct ADL_MIDIPlayer *device, double secon return -1.0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); + double ret = play->Tick(seconds, granulality); + play->TickIterators(seconds); + return ret; +#else + ADL_UNUSED(device); + ADL_UNUSED(seconds); + ADL_UNUSED(granulality); + return -1.0; +#endif +} + +ADLMIDI_EXPORT double adl_tickEventsOnly(struct ADL_MIDIPlayer *device, double seconds, double granulality) +{ +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + if(!device) + return -1.0; + MidiPlayer *play = GET_MIDI_PLAYER(device); + assert(play); return play->Tick(seconds, granulality); #else ADL_UNUSED(device); @@ -1602,6 +1662,14 @@ ADLMIDI_EXPORT double adl_tickEvents(struct ADL_MIDIPlayer *device, double secon #endif } +ADLMIDI_EXPORT void adl_tickIterators(struct ADL_MIDIPlayer *device, double seconds) +{ + if(!device) + return; + MidiPlayer *play = GET_MIDI_PLAYER(device); + play->TickIterators(seconds); +} + ADLMIDI_EXPORT int adl_atEnd(struct ADL_MIDIPlayer *device) { #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index e3f5819..ce02006 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -261,7 +261,7 @@ bool MIDIplay::LoadMIDI_post() resetMIDIDefaults(); m_setup.tick_skip_samples_delay = 0; - synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip + chipReset(); // Reset OPL3 chip //opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously) m_chipChannels.clear(); m_chipChannels.resize(synth.m_numChannels); diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 2ce2ed0..8af0674 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -76,6 +76,13 @@ MIDIplay::MIDIplay(unsigned long sampleRate): m_setup.emulator = adl_getLowestEmulator(); m_setup.runAtPcmRate = false; +#ifdef ADLMIDI_ENABLE_HW_SERIAL + m_setup.serial = false; + m_setup.serialName = std::string(); + m_setup.serialBaud = 0; + m_setup.serialProtocol = 0; +#endif + m_setup.PCM_RATE = sampleRate; m_setup.mindelay = 1.0 / (double)m_setup.PCM_RATE; m_setup.maxdelay = 512.0 / (double)m_setup.PCM_RATE; @@ -155,7 +162,7 @@ void MIDIplay::applySetup() else adlCalculateFourOpChannels(this, true); - synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); + chipReset(); m_chipChannels.clear(); m_chipChannels.resize(synth.m_numChannels); @@ -169,7 +176,7 @@ void MIDIplay::partialReset() realTime_panic(); m_setup.tick_skip_samples_delay = 0; synth.m_runAtPcmRate = m_setup.runAtPcmRate; - synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); + chipReset(); m_chipChannels.clear(); m_chipChannels.resize((size_t)synth.m_numChannels); resetMIDIDefaults(); @@ -193,6 +200,21 @@ void MIDIplay::resetMIDI() caugh_missing_banks_percussion.clear(); } +void MIDIplay::chipReset() +{ + Synth &synth = *m_synth; + +#ifdef ADLMIDI_ENABLE_HW_SERIAL + if(m_setup.serial) + { + synth.resetSerial(m_setup.serialName, m_setup.serialBaud, m_setup.serialProtocol); + return; + } +#endif + + synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); +} + void MIDIplay::resetMIDIDefaults(int offset) { Synth &synth = *m_synth; @@ -252,7 +274,11 @@ void MIDIplay::TickIterators(double s) updateVibrato(s); updateArpeggio(s); + #if !defined(ADLMIDI_AUDIO_TICK_HANDLER) +# ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + s *= m_sequencer->getTempoMultiplier(); // Glide will follow the tempo +# endif updateGlide(s); #endif } diff --git a/src/adlmidi_midiplay.hpp b/src/adlmidi_midiplay.hpp index 4ac5b3d..5b5bc45 100644 --- a/src/adlmidi_midiplay.hpp +++ b/src/adlmidi_midiplay.hpp @@ -76,6 +76,7 @@ public: void resetMIDI(); private: + void chipReset(); void resetMIDIDefaults(int offset = 0); public: @@ -526,6 +527,14 @@ public: { int emulator; bool runAtPcmRate; + +#ifdef ADLMIDI_ENABLE_HW_SERIAL + bool serial; + std::string serialName; + unsigned int serialBaud; + unsigned int serialProtocol; +#endif + unsigned int bankId; int numFourOps; unsigned int numChips; diff --git a/src/adlmidi_opl3.cpp b/src/adlmidi_opl3.cpp index 5f9ecdc..4638c5a 100644 --- a/src/adlmidi_opl3.cpp +++ b/src/adlmidi_opl3.cpp @@ -60,6 +60,11 @@ static const unsigned OPLBase = 0x388; # ifndef ADLMIDI_DISABLE_JAVA_EMULATOR # include "chips/java_opl3.h" # endif + +// HW OPL Serial +# ifdef ADLMIDI_ENABLE_HW_SERIAL +# include "chips/opl_serial_port.h" +# endif #endif static const unsigned adl_emulatorSupport = 0 @@ -860,6 +865,11 @@ static OplInstMeta makeEmptyInstrument() const OplInstMeta OPL3::m_emptyInstrument = makeEmptyInstrument(); OPL3::OPL3() : +#ifdef ADLMIDI_ENABLE_HW_SERIAL + m_serial(false), + m_serialBaud(0), + m_serialProtocol(0), +#endif m_numChips(1), m_numFourOps(0), m_deepTremoloMode(false), @@ -1739,6 +1749,11 @@ void OPL3::reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler) m_chips.resize(m_numChips, AdlMIDI_SPtr<OPLChipBase>()); #endif +#ifdef ADLMIDI_ENABLE_HW_SERIAL + if(emulator >= 0) // If less than zero - it's hardware synth! + m_serial = false; +#endif + const struct OplTimbre defaultInsCache = { 0x1557403,0x005B381, 0x49,0x80, 0x4, +0 }; m_numChannels = m_numChips * NUM_OF_CHANNELS; m_insCache.resize(m_numChannels, defaultInsCache); @@ -1754,16 +1769,21 @@ void OPL3::reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler) m_channelCategory[p++] = ChanCat_Rhythm_Secondary; } - static const uint16_t data[] = - { - 0x004, 96, 0x004, 128, // Pulse timer - 0x105, 0, 0x105, 1, 0x105, 0, // Pulse OPL3 enable - 0x001, 32, 0x105, 1 // Enable wave, OPL3 extensions - }; // size_t fours = m_numFourOps; for(size_t i = 0; i < m_numChips; ++i) { +#ifdef ADLMIDI_ENABLE_HW_SERIAL + if(emulator < 0) + { + OPL_SerialPort *serial = new OPL_SerialPort; + serial->connectPort(m_serialName, m_serialBaud, m_serialProtocol); + m_chips[i].reset(serial); + initChip(i); + break; // Only one REAL chip! + } +#endif + #ifndef ADLMIDI_HW_OPL OPLChipBase *chip; switch(emulator) @@ -1795,23 +1815,52 @@ void OPL3::reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler) break; #endif } + m_chips[i].reset(chip); chip->setChipId((uint32_t)i); chip->setRate((uint32_t)PCM_RATE); + if(m_runAtPcmRate) chip->setRunningAtPcmRate(true); + # if defined(ADLMIDI_AUDIO_TICK_HANDLER) chip->setAudioTickHandlerInstance(audioTickHandler); # endif #endif // ADLMIDI_HW_OPL - /* Clean-up channels from any playing junk sounds */ - for(size_t a = 0; a < OPL3_CHANNELS_RHYTHM_BASE; ++a) - writeRegI(i, 0xB0 + g_channelsMap[a], 0x00); - for(size_t a = 0; a < sizeof(data) / sizeof(*data); a += 2) - writeRegI(i, data[a], (data[a + 1])); + initChip(i); } updateChannelCategories(); silenceAll(); } + +void OPL3::initChip(size_t chip) +{ + static const uint16_t data[] = + { + 0x004, 96, 0x004, 128, // Pulse timer + 0x105, 0, 0x105, 1, 0x105, 0, // Pulse OPL3 enable + 0x001, 32, 0x105, 1 // Enable wave, OPL3 extensions + }; + + /* Clean-up channels from any playing junk sounds */ + for(size_t a = 0; a < OPL3_CHANNELS_RHYTHM_BASE; ++a) + writeRegI(chip, 0xB0 + g_channelsMap[a], 0x00); + + for(size_t a = 0; a < sizeof(data) / sizeof(*data); a += 2) + writeRegI(chip, data[a], (data[a + 1])); +} + +#ifdef ADLMIDI_ENABLE_HW_SERIAL +void OPL3::resetSerial(const std::string &serialName, unsigned int baud, unsigned int protocol) +{ + m_serial = true; + m_serialName = serialName; + m_serialBaud = baud; + m_serialProtocol = protocol; + m_numChips = 1; // Only one chip! + m_softPanning = false; // Soft-panning doesn't work on hardware + reset(-1, 0, NULL); +} +#endif diff --git a/src/adlmidi_opl3.hpp b/src/adlmidi_opl3.hpp index ca35157..abdc20d 100644 --- a/src/adlmidi_opl3.hpp +++ b/src/adlmidi_opl3.hpp @@ -74,6 +74,13 @@ private: //! Cached BD registry value (flags register: DeepTremolo, DeepVibrato, and RhythmMode) std::vector<uint32_t> m_regBD; +#ifdef ADLMIDI_ENABLE_HW_SERIAL + bool m_serial; + std::string m_serialName; + unsigned m_serialBaud; + unsigned m_serialProtocol; +#endif + public: /** * @brief MIDI bank entry @@ -321,12 +328,12 @@ public: */ ADLMIDI_VolumeModels getVolumeScaleModel(); - #ifndef ADLMIDI_HW_OPL +#ifndef ADLMIDI_HW_OPL /** * @brief Clean up all running emulated chip instances */ void clearChips(); - #endif +#endif /** * @brief Reset chip properties and initialize them @@ -335,6 +342,18 @@ public: * @param audioTickHandler PCM-accurate clock hook */ void reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler); + + void initChip(size_t chip); + +#ifdef ADLMIDI_ENABLE_HW_SERIAL + /** + * @brief Reset chip properties for hardware use + * @param emulator + * @param PCM_RATE + * @param audioTickHandler + */ + void resetSerial(const std::string &serialName, unsigned int baud, unsigned int protocol); +#endif }; /** diff --git a/src/adlmidi_sequencer.cpp b/src/adlmidi_sequencer.cpp index 39361cb..d0b3ce5 100644 --- a/src/adlmidi_sequencer.cpp +++ b/src/adlmidi_sequencer.cpp @@ -156,8 +156,8 @@ double MIDIplay::Tick(double s, double granularity) MidiSequencer &seqr = *m_sequencer; double ret = seqr.Tick(s, granularity); - s *= seqr.getTempoMultiplier(); - TickIterators(s); + // s *= seqr.getTempoMultiplier(); + // TickIterators(s); return ret; } 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 <fcntl.h> +#include <termios.h> +#include <unistd.h> +#include <cstring> +#include <cstdio> +#include <errno.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#endif + +#include <string> + + +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<uint8_t>(0x21 << 1), 0x12, + static_cast<uint8_t>(port1 ? 0xe5 : 0xe1), static_cast<uint8_t>(addr & 0xff), + static_cast<uint8_t>(port1 ? 0xe7 : 0xe3), static_cast<uint8_t>(data), + 0xfb, static_cast<uint8_t>(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 <string> +#include "opl_chip_base.h" + +class ChipSerialPort; + +class OPL_SerialPort : public OPLChipBaseT<OPL_SerialPort> +{ +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 |