diff options
-rw-r--r-- | CMakeLists.txt | 12 | ||||
-rw-r--r-- | include/adlmidi.h | 57 | ||||
-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 | 77 | ||||
-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 | 338 | ||||
-rw-r--r-- | src/chips/opl_serial_port.cpp | 166 | ||||
-rw-r--r-- | src/chips/opl_serial_port.h | 65 | ||||
-rw-r--r-- | utils/midiplay/CMakeLists.txt | 4 | ||||
-rw-r--r-- | utils/midiplay/adlmidiplay.cpp | 1251 | ||||
-rw-r--r-- | utils/midiplay/utf8main.cmake | 10 | ||||
-rw-r--r-- | utils/midiplay/utf8main.h | 41 | ||||
-rw-r--r-- | utils/midiplay/utf8main_win32.cpp | 95 | ||||
-rw-r--r-- | utils/vlc_codec/libadlmidi.c | 102 |
18 files changed, 1962 insertions, 394 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fd89e9..dcfddc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,6 +189,7 @@ option(USE_DOSBOX_EMULATOR "Use DosBox 0.74 OPL3 emulator (semi-accurate, sugge option(USE_NUKED_EMULATOR "Use Nuked OPL3 emulator (most accurate, powerful)" ${DEFAULT_HEAVY_EMULATORS}) option(USE_OPAL_EMULATOR "Use Opal emulator (inaccurate)" ${DEFAULT_HEAVY_EMULATORS}) option(USE_JAVA_EMULATOR "Use JavaOPL emulator" ${DEFAULT_HEAVY_EMULATORS}) +option(USE_HW_SERIAL "Use the hardware OPL3 chip via Serial on modern systems" OFF) option(WITH_GENADLDATA "Build and run full rebuild of embedded banks cache" OFF) option(WITH_GENADLDATA_COMMENTS "Enable comments in a generated embedded instruments cache file" OFF) @@ -286,6 +287,15 @@ function(handle_options targetLib) target_compile_definitions(${targetLib} PUBLIC ADLMIDI_DISABLE_JAVA_EMULATOR) endif() + if(USE_HW_SERIAL) + set(HAS_EMULATOR TRUE) + target_sources(${targetLib} PRIVATE + ${libADLMIDI_SOURCE_DIR}/src/chips/opl_serial_port.cpp + ) + target_compile_definitions(${targetLib} PRIVATE ENABLE_HW_OPL_SERIAL_PORT) + target_compile_definitions(${targetLib} PUBLIC ADLMIDI_ENABLE_HW_SERIAL) + endif() + if(NOT HAS_EMULATOR) message(FATAL_ERROR "No emulators enabled! You must enable at least one emulator!") endif() @@ -414,6 +424,7 @@ function(libADLMIDI_find_SDL2) add_library(ADLMIDI_SDL2 INTERFACE) if(TARGET SDL2::SDL2) target_link_libraries(ADLMIDI_SDL2 INTERFACE SDL2::SDL2) + target_include_directories(ADLMIDI_SDL2 INTERFACE ${SDL2_INCLUDE_DIRS}) else() string(STRIP ${SDL2_LIBRARIES} SDL2_LIBRARIES) target_include_directories(ADLMIDI_SDL2 INTERFACE ${SDL2_INCLUDE_DIRS}) @@ -547,6 +558,7 @@ message("USE_DOSBOX_EMULATOR = ${USE_DOSBOX_EMULATOR}") message("USE_NUKED_EMULATOR = ${USE_NUKED_EMULATOR}") message("USE_OPAL_EMULATOR = ${USE_OPAL_EMULATOR}") message("USE_JAVA_EMULATOR = ${USE_JAVA_EMULATOR}") +message("USE_HW_SERIAL = ${USE_HW_SERIAL}") message("===== Utils and extras =====") message("WITH_GENADLDATA = ${WITH_GENADLDATA}") diff --git a/include/adlmidi.h b/include/adlmidi.h index 3dd9d00..e78e920 100644 --- a/include/adlmidi.h +++ b/include/adlmidi.h @@ -738,6 +738,30 @@ typedef struct { extern ADLMIDI_DECLSPEC int adl_setRunAtPcmRate(struct ADL_MIDIPlayer *device, int enabled); /** + * @brief The list of serial port protocols + */ +enum ADL_SerialProtocol +{ + ADLMIDI_SerialProtocol_Unknown = 0, + ADLMIDI_SerialProtocol_ArduinoOPL2, + ADLMIDI_SerialProtocol_NukeYktOPL3, + ADLMIDI_SerialProtocol_RetroWaveOPL3, + ADLMIDI_SerialProtocol_END +}; + +/** + * @brief Switch the synthesizer into hardware mode using Serial port + * @param name The name of the serial port device (it may look different on various platforms. On UNIX-like systems don't type the /dev/ prefix: only name). + * @param baud The baud speed of the serial port + * @param protocol The binary protocol used to communicate the device (#ADL_SerialProtocol) + * @return 0 on success, <0 when any error has occurred + */ +extern ADLMIDI_DECLSPEC int adl_switchSerialHW(struct ADL_MIDIPlayer *device, + const char *name, + unsigned baud, + unsigned protocol); + +/** * @brief Set 4-bit device identifier. Used by the SysEx processor. * @param device Instance of the library * @param id 4-bit device identifier @@ -1152,6 +1176,39 @@ extern ADLMIDI_DECLSPEC int adl_generateFormat(struct ADL_MIDIPlayer *device, i */ extern ADLMIDI_DECLSPEC double adl_tickEvents(struct ADL_MIDIPlayer *device, double seconds, double granulality); +/** + * @brief Periodic tick handler without iterators. + * + * Unlike adl_tickEvents(), it doesn't handles iterators, you need to perform + * them naually via adl_tickIterators(). + * + * Notice: The function is provided to use it with Hardware OPL3 mode or for the purpose to iterate + * MIDI playback without of sound generation. + * + * DON'T USE IT TOGETHER WITH adl_play() and adl_playFormat() calls + * as there are all using this function internally!!! + * + * @param device Instance of the library + * @param seconds Previous delay. On a first moment, pass the `0.0` + * @param granulality Minimal size of one MIDI tick in seconds. + * @return desired number of seconds until next call. Pass this value into `seconds` field in next time + */ +extern ADLMIDI_DECLSPEC double adl_tickEventsOnly(struct ADL_MIDIPlayer *device, double seconds, double granulality); + + +/** + * @brief Periodic tick hander for the real-time hardware output + * + * This function runs a single step of vibrato, auto-arpeggio, and the portamento of @seconds duration. + * + * When running the libADLMIDI as a real-time driver for the ral hardware, call + * this function from the timer and specify the @seconds value with a delay of the single cycle. + * + * @param device Instance of the library + * @param seconds Previous delay. On a first moment, pass the `0.0` + */ +extern ADLMIDI_DECLSPEC void adl_tickIterators(struct ADL_MIDIPlayer *device, double seconds); + 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..b934475 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,58 @@ 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 + 0x08, 0 // CSW/Note Sel + }; + + /* Clean-up channels from any playing junk sounds */ + for(size_t a = 0; a < OPL3_CHANNELS_RHYTHM_BASE; ++a) + { + writeRegI(chip, 0x20 + g_operatorsMap[a * 2], 0x00); + writeRegI(chip, 0x20 + g_operatorsMap[(a * 2) + 1], 0x00); + writeRegI(chip, 0xA0 + g_channelsMap[a], 0x00); + 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..2583a4b --- /dev/null +++ b/src/chips/opl_serial_misc.h @@ -0,0 +1,338 @@ +/* + * 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> +#include <sys/ioctl.h> +#endif + +#ifdef __APPLE__ +#include <IOKit/serial/ioss.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#endif + +#include <string> + + +class ChipSerialPortBase +{ +public: + ChipSerialPortBase() {} + virtual ~ChipSerialPortBase() {} + + virtual bool isOpen() + { + return false; + } + + virtual void close() {} + + virtual bool open(const std::string &/*portName*/, unsigned /*baudRate*/) + { + return false; + } + + virtual 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 + +#if defined (BSD) || defined(__FreeBSD__) + cfsetispeed(&m_portSetup, baudRate); + cfsetospeed(&m_portSetup, baudRate); +#elif !defined(__APPLE__) + cfsetospeed(&m_portSetup, baud2enum(baudRate)); +#endif + + 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; + } + +#ifdef __APPLE__ + if(ioctl(m_port, IOSSIOSPEED, &baudRate) == -1) + { + std::fprintf(stderr, "-- OPL Serial ERROR: Failed to set MacOS specific tty attributes for `%s': %s", portPath.c_str(), strerror(errno)); + std::fflush(stderr); + close(); + return false; + } +#endif + + 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..1607d8c --- /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]; + ChipSerialPortBase *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..346c68e --- /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 ChipSerialPortBase; + +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: + ChipSerialPortBase *m_port; + int m_protocol; +}; + +#endif // ENABLE_HW_OPL_SERIAL_PORT + +#endif // OPL_SERIAL_PORT_H diff --git a/utils/midiplay/CMakeLists.txt b/utils/midiplay/CMakeLists.txt index 52d0128..3d5eeda 100644 --- a/utils/midiplay/CMakeLists.txt +++ b/utils/midiplay/CMakeLists.txt @@ -7,9 +7,12 @@ if(NOT ADLMIDI_DOS AND NOT MIDIPLAY_WAVE_ONLY) endif() endif() +include(utf8main.cmake) + set(ADLMIDI_PLAY_SRC adlmidiplay.cpp wave_writer.c + ${UTF8MAIN_SRCS} ) if(USE_SDL2_AUDIO) @@ -30,6 +33,7 @@ add_executable(adlmidiplay ${ADLMIDI_PLAY_SRC}) if(USE_SDL2_AUDIO) target_link_libraries(adlmidiplay PRIVATE ADLMIDI_SDL2) + target_compile_definitions(adlmidiplay PRIVATE ADLMIDI_USE_SDL2) elseif(WIN32) target_link_libraries(adlmidiplay PRIVATE winmm) endif() diff --git a/utils/midiplay/adlmidiplay.cpp b/utils/midiplay/adlmidiplay.cpp index fe0a758..cef025a 100644 --- a/utils/midiplay/adlmidiplay.cpp +++ b/utils/midiplay/adlmidiplay.cpp @@ -34,6 +34,60 @@ #include <algorithm> #include <signal.h> #include <stdint.h> +#include "utf8main.h" + +#if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) +# ifdef ADLMIDI_USE_SDL2 +# include <SDL2/SDL_timer.h> +#ifdef __APPLE__ +# include <unistd.h> +#endif + +static inline double s_getTime() +{ + return SDL_GetTicks64() / 1000.0; +} + +static inline void s_sleepU(double s) +{ +#ifdef __APPLE__ + // For unknown reasons, any sleep functions to way WAY LONGER than requested + // So, implementing an own one. + static double debt = 0.0; + double target = s_getTime() + s - debt; + + while(s_getTime() < target) + usleep(1000); + + debt = s_getTime() - target; +#else + SDL_Delay((Uint32)(s * 1000)); +#endif +} +# else + +# include <time.h> +# include <unistd.h> +# include <assert.h> +static inline double s_getTime() +{ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return (t.tv_nsec + (t.tv_sec * 1000000000)) / 1000000000.0; +} + +static inline void s_sleepU(double s) +{ + static double debt = 0.0; + double target = s_getTime() + s - debt; + + while(s_getTime() < target) + usleep(1000); + + debt = s_getTime() - target; +} +# endif +#endif #ifdef DEBUG_SONG_SWITCHING #include <unistd.h> @@ -209,17 +263,89 @@ typedef std::deque<uint8_t> AudioBuff; static AudioBuff g_audioBuffer; static MutexType g_audioBuffer_lock; static ADLMIDI_AudioFormat g_audioFormat; +static float g_gaining = 2.0f; + +static void applyGain(uint8_t *buffer, size_t bufferSize) +{ + size_t i; + + switch(g_audioFormat.type) + { + case ADLMIDI_SampleType_S8: + { + int8_t *buf = reinterpret_cast<int8_t *>(buffer); + size_t samples = bufferSize; + for(i = 0; i < samples; ++i) + *(buf++) *= g_gaining; + break; + } + case ADLMIDI_SampleType_U8: + { + uint8_t *buf = buffer; + size_t samples = bufferSize; + for(i = 0; i < samples; ++i) + { + int8_t s = static_cast<int8_t>(static_cast<int32_t>(*buf) + (-0x7f - 1)) * g_gaining; + *(buf++) = static_cast<uint8_t>(static_cast<int32_t>(s) - (-0x7f - 1)); + } + break; + } + case ADLMIDI_SampleType_S16: + { + int16_t *buf = reinterpret_cast<int16_t *>(buffer); + size_t samples = bufferSize / g_audioFormat.containerSize; + for(i = 0; i < samples; ++i) + *(buf++) *= g_gaining; + break; + } + case ADLMIDI_SampleType_U16: + { + uint16_t *buf = reinterpret_cast<uint16_t *>(buffer); + size_t samples = bufferSize / g_audioFormat.containerSize; + for(i = 0; i < samples; ++i) + { + int16_t s = static_cast<int16_t>(static_cast<int32_t>(*buf) + (-0x7fff - 1)) * g_gaining; + *(buf++) = static_cast<uint16_t>(static_cast<int32_t>(s) - (-0x7fff - 1)); + } + break; + } + case ADLMIDI_SampleType_S32: + { + int32_t *buf = reinterpret_cast<int32_t *>(buffer); + size_t samples = bufferSize / g_audioFormat.containerSize; + for(i = 0; i < samples; ++i) + *(buf++) *= g_gaining; + break; + } + case ADLMIDI_SampleType_F32: + { + float *buf = reinterpret_cast<float *>(buffer); + size_t samples = bufferSize / g_audioFormat.containerSize; + for(i = 0; i < samples; ++i) + *(buf++) *= g_gaining; + break; + } + default: + break; + } +} static void SDL_AudioCallbackX(void *, uint8_t *stream, int len) { + unsigned ate = static_cast<unsigned>(len); // number of bytes + audio_lock(); //short *target = (short *) stream; - g_audioBuffer_lock.Lock(); - unsigned ate = static_cast<unsigned>(len); // number of bytes + g_audioBuffer_lock.Lock(); + if(ate > g_audioBuffer.size()) ate = (unsigned)g_audioBuffer.size(); + for(unsigned a = 0; a < ate; ++a) stream[a] = g_audioBuffer[a]; + + applyGain(stream, len); + g_audioBuffer.erase(g_audioBuffer.begin(), g_audioBuffer.begin() + ate); g_audioBuffer_lock.Unlock(); audio_unlock(); @@ -305,9 +431,12 @@ static bool is_number(const std::string &s) return !s.empty() && it == s.end(); } -static void printError(const char *err) +static void printError(const char *err, const char *what = NULL) { - std::fprintf(stderr, "\nERROR: %s\n\n", err); + if(what) + std::fprintf(stderr, "\nERROR (%s): %s\n\n", what, err); + else + std::fprintf(stderr, "\nERROR: %s\n\n", err); flushout(stderr); } @@ -439,6 +568,651 @@ static inline void secondsToHMSM(double seconds_full, char *hmsm_buffer, size_t snprintf(hmsm_buffer, hmsm_buffer_size, "%02u:%02u,%03u", minutes, seconds, milliseconds); } + +static struct TimeCounter +{ + char posHMS[25]; + char totalHMS[25]; + char loopStartHMS[25]; + char loopEndHMS[25]; + char realHMS[25]; + + bool hasLoop; + uint64_t milliseconds_prev; + int printsCounter; + int printsCounterPeriod; + int complete_prev; + double totalTime; + + double realTimeStart; + +#ifdef HARDWARE_OPL3 + unsigned newTimerFreq; + unsigned timerPeriod; + int haveYield; + int haveDosIdle; + unsigned int ring; + unsigned long BIOStimer_begin; + + unsigned long timerNext; + + enum wmethod + { + WM_NONE, + WM_YIELD, + WM_IDLE, + WM_HLT + } idleMethod; + +#endif + + TimeCounter() + { + hasLoop = false; + totalTime = 0.0; + milliseconds_prev = ~0u; + printsCounter = 0; + complete_prev = -1; + +#ifndef HARDWARE_OPL3 + printsCounterPeriod = 1; +#else + printsCounterPeriod = 20; + setDosTimerHZ(209); + haveYield = 0; + haveDosIdle = 0; + ring = 0; + idleMethod = WM_NONE; + + timerNext = 0; +#endif + } + +#ifdef HARDWARE_OPL3 + void initDosTimer() + { +# ifdef __DJGPP__ + /* determine protection ring */ + __asm__ ("mov %%cs, %0\n\t" + "and $3, %0" : "=r" (ring)); + + errno = 0; + __dpmi_yield(); + haveYield = errno ? 0 : 1; + + if(!haveYield) + { + __dpmi_regs regs; + regs.x.ax = 0x1680; + __dpmi_int(0x28, ®s); + haveDosIdle = regs.h.al ? 0 : 1; + + if(haveDosIdle) + idleMethod = WM_IDLE; + else if(ring == 0) + idleMethod = WM_HLT; + else + idleMethod = WM_NONE; + } + else + { + idleMethod = WM_YIELD; + } + + const char *method; + switch(idleMethod) + { + default: + case WM_NONE: + method = "none"; + break; + case WM_YIELD: + method = "yield"; + break; + case WM_IDLE: + method = "idle"; + break; + case WM_HLT: + method = "hlt"; + break; + } + + std::fprintf(stdout, " - [DOS] Using idle method: %s\n", method); +# endif + } + + void setDosTimerHZ(unsigned timer) + { + newTimerFreq = timer; + timerPeriod = 0x1234DDul / newTimerFreq; + } + + void flushDosTimer() + { +# ifdef __DJGPP__ + outportb(0x43, 0x34); + outportb(0x40, timerPeriod & 0xFF); + outportb(0x40, timerPeriod >> 8); +# endif + +# ifdef __WATCOMC__ + outp(0x43, 0x34); + outp(0x40, TimerPeriod & 0xFF); + outp(0x40, TimerPeriod >> 8); +# endif + + BIOStimer_begin = BIOStimer; + + std::fprintf(stdout, " - [DOS] Running clock with %d hz\n", newTimerFreq); + } + + void restoreDosTimer() + { +# ifdef __DJGPP__ + // Fix the skewed clock and reset BIOS tick rate + _farpokel(_dos_ds, 0x46C, BIOStimer_begin + (BIOStimer - BIOStimer_begin) * (0x1234DD / 65536.0) / newTimerFreq); + + //disable(); + outportb(0x43, 0x34); + outportb(0x40, 0); + outportb(0x40, 0); + //enable(); +# endif + +# ifdef __WATCOMC__ + outp(0x43, 0x34); + outp(0x40, 0); + outp(0x40, 0); +# endif + } + + void waitDosTimer() + { +//__asm__ volatile("sti\nhlt"); +//usleep(10000); +# ifdef __DJGPP__ + switch(idleMethod) + { + default: + case WM_NONE: + if(timerNext != 0) + while(BIOStimer < timerNext); + timerNext = BIOStimer + 1; + break; + + case WM_YIELD: + __dpmi_yield(); + break; + + case WM_IDLE: + { + __dpmi_regs regs; + + /* the DOS Idle call is documented to return immediately if no other + * program is ready to run, therefore do one HLT if we can */ + if(ring == 0) + __asm__ volatile ("hlt"); + + regs.x.ax = 0x1680; + __dpmi_int(0x28, ®s); + if (regs.h.al) + errno = ENOSYS; + break; + } + + case WM_HLT: + __asm__ volatile("hlt"); + break; + } +# endif +# ifdef __WATCOMC__ + //dpmi_dos_yield(); + mch_delay((unsigned int)(tick_delay * 1000.0)); +# endif + } +#endif + + void setTotal(double total) + { + totalTime = total; + secondsToHMSM(total, totalHMS, 25); + realTimeStart = s_getTime(); + secondsToHMSM(s_getTime() - realTimeStart, realHMS, 25); + } + + void setLoop(double loopStart, double loopEnd) + { + hasLoop = false; + + if(loopStart >= 0.0 && loopEnd >= 0.0) + { + secondsToHMSM(loopStart, loopStartHMS, 25); + secondsToHMSM(loopEnd, loopEndHMS, 25); + hasLoop = true; + } + } + + void clearLineR() + { + std::fprintf(stdout, " \r"); + flushout(stdout); + } + + void printTime(double pos) + { + uint64_t milliseconds = static_cast<uint64_t>(pos * 1000.0); + + if(milliseconds != milliseconds_prev) + { + if(printsCounter >= printsCounterPeriod) + { + printsCounter = -1; + secondsToHMSM(pos, posHMS, 25); + secondsToHMSM(s_getTime() - realTimeStart, realHMS, 25); + std::fprintf(stdout, " \r"); + std::fprintf(stdout, "Time position: %s / %s [Real time: %s]\r", posHMS, totalHMS, realHMS); + flushout(stdout); + milliseconds_prev = milliseconds; + } + printsCounter++; + } + } + + void printProgress(double pos) + { + int complete = static_cast<int>(std::floor(100.0 * pos / totalTime)); + + if(complete_prev != complete) + { + std::fprintf(stdout, " \r"); + std::fprintf(stdout, "Recording WAV... [%d%% completed]\r", complete); + flushout(stdout); + complete_prev = complete; + } + } + + void clearLine() + { + std::fprintf(stdout, " \n\n"); + flushout(stdout); + } + +} s_timeCounter; + + + +#if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) +static void runHWSerialLoop(ADL_MIDIPlayer *myDevice) +{ + double tick_delay = 0.00000001; + double tick_wait = 0.0; + double timeBegL, timeEndL; +#if _WIN32 + const double minDelay = 0.050; // On Windows, the Serial bandwith is WAY SLOWER, so, bigger granuality. +#else + const double minDelay = 0.005; +#endif + double eat_delay; + // bool tickSkip = true; + + s_timeCounter.clearLineR(); + + while(!stop) + { + timeBegL = s_getTime(); + tick_delay = adl_tickEvents(myDevice, tick_delay < minDelay ? tick_delay : minDelay, minDelay / 10.0); + // adl_tickIterators(myDevice, minDelay); +# ifndef DEBUG_TRACE_ALL_EVENTS + s_timeCounter.printTime(adl_positionTell(myDevice)); +# endif + timeEndL = s_getTime(); + + eat_delay = timeEndL - timeBegL; + + if(tick_delay < minDelay) + tick_wait = tick_delay - eat_delay; + else + tick_wait = minDelay - eat_delay; + + if(adl_atEnd(myDevice) && tick_delay <= 0) + stop = true; + + if(tick_wait > 0.0) + s_sleepU(tick_wait); + +#if 0 + timeBegL = s_getTime(); + + tick_delay = adl_tickEventsOnly(myDevice, tick_delay, 0.000000001); + adl_tickIterators(myDevice, tick_delay < minDelay ? tick_delay : minDelay); + + if(adl_atEnd(myDevice) && tick_delay <= 0) + stop = true; + +# ifndef DEBUG_TRACE_ALL_EVENTS + s_timeCounter.printTime(adl_positionTell(myDevice)); +# endif + + timeEndL = s_getTime(); + + if(timeEndL < timeBegL) + timeEndL = timeBegL; + + eat_delay = timeEndL - timeBegL; + tick_wait += tick_delay - eat_delay; + + if(tick_wait > 0.0) + { + if(tick_wait < minDelay) + { + if(tick_wait > 0.0) + s_sleepU(tick_wait); + tick_wait -= minDelay; + } + + while(tick_wait > 0.0) + { + timeBegL = s_getTime(); + if(!tickSkip) + adl_tickIterators(myDevice, minDelay); + else + tickSkip = false; + timeEndL = s_getTime(); + + if(timeEndL < timeBegL) + timeEndL = timeBegL; + + double microDelay = minDelay - (timeEndL - timeBegL); + + if(microDelay > 0.0) + s_sleepU(microDelay); + + tick_wait -= minDelay; + } + } +#endif + } + + s_timeCounter.clearLine(); + + adl_panic(myDevice); //Shut up all sustaining notes +} +#endif // ADLMIDI_ENABLE_HW_SERIAL + + +#ifndef HARDWARE_OPL3 +# ifndef OUTPUT_WAVE_ONLY +static int runAudioLoop(ADL_MIDIPlayer *myDevice, AudioOutputSpec &spec) +{ + //const unsigned MaxSamplesAtTime = 512; // 512=dbopl limitation + // How long is SDL buffer, in seconds? + // The smaller the value, the more often SDL_AudioCallBack() + // is called. + //const double AudioBufferLength = 0.08; + + // How much do WE buffer, in seconds? The smaller the value, + // the more prone to sound chopping we are. + const double OurHeadRoomLength = 0.1; + // The lag between visual content and audio content equals + // the sum of these two buffers. + + AudioOutputSpec obtained; + + // Set up SDL + if(audio_init(&spec, &obtained, SDL_AudioCallbackX) < 0) + { + std::fprintf(stderr, "\nERROR: Couldn't open audio: %s\n\n", audio_get_error()); + return 1; + } + + if(spec.samples != obtained.samples) + { + std::fprintf(stderr, " - Audio wanted (format=%s,samples=%u,rate=%u,channels=%u);\n" + " - Audio obtained (format=%s,samples=%u,rate=%u,channels=%u)\n", + audio_format_to_str(spec.format, spec.is_msb), spec.samples, spec.freq, spec.channels, + audio_format_to_str(obtained.format, obtained.is_msb), obtained.samples, obtained.freq, obtained.channels); + } + + switch(obtained.format) + { + case ADLMIDI_SampleType_S8: + g_audioFormat.type = ADLMIDI_SampleType_S8; + g_audioFormat.containerSize = sizeof(int8_t); + g_audioFormat.sampleOffset = sizeof(int8_t) * 2; + break; + case ADLMIDI_SampleType_U8: + g_audioFormat.type = ADLMIDI_SampleType_U8; + g_audioFormat.containerSize = sizeof(uint8_t); + g_audioFormat.sampleOffset = sizeof(uint8_t) * 2; + break; + case ADLMIDI_SampleType_S16: + g_audioFormat.type = ADLMIDI_SampleType_S16; + g_audioFormat.containerSize = sizeof(int16_t); + g_audioFormat.sampleOffset = sizeof(int16_t) * 2; + break; + case ADLMIDI_SampleType_U16: + g_audioFormat.type = ADLMIDI_SampleType_U16; + g_audioFormat.containerSize = sizeof(uint16_t); + g_audioFormat.sampleOffset = sizeof(uint16_t) * 2; + break; + case ADLMIDI_SampleType_S32: + g_audioFormat.type = ADLMIDI_SampleType_S32; + g_audioFormat.containerSize = sizeof(int32_t); + g_audioFormat.sampleOffset = sizeof(int32_t) * 2; + break; + case ADLMIDI_SampleType_F32: + g_audioFormat.type = ADLMIDI_SampleType_F32; + g_audioFormat.containerSize = sizeof(float); + g_audioFormat.sampleOffset = sizeof(float) * 2; + break; + } + + +# ifdef DEBUG_SONG_CHANGE + int delayBeforeSongChange = 50; + std::fprintf(stdout, "DEBUG: === Random song set test is active! ===\n"); + flushout(stdout); +# endif + +# ifdef DEBUG_SEEKING_TEST + int delayBeforeSeek = 50; + std::fprintf(stdout, "DEBUG: === Random position set test is active! ===\n"); + flushout(stdout); +# endif + + size_t got; + uint8_t buff[16384]; + + audio_start(); + + s_timeCounter.clearLineR(); + + while(!stop) + { + got = (size_t)adl_playFormat(myDevice, 4096, + buff, + buff + g_audioFormat.containerSize, + &g_audioFormat) * g_audioFormat.containerSize; + if(got <= 0) + break; + +# ifdef DEBUG_TRACE_ALL_CHANNELS + enum { TerminalColumns = 80 }; + char channelText[TerminalColumns + 1]; + char channelAttr[TerminalColumns + 1]; + adl_describeChannels(myDevice, channelText, channelAttr, sizeof(channelText)); + std::fprintf(stdout, "%*s\r", TerminalColumns, ""); // erase the line + std::fprintf(stdout, "%s\n", channelText); +# endif + +# ifndef DEBUG_TRACE_ALL_EVENTS + s_timeCounter.printTime(adl_positionTell(myDevice)); +# endif + + g_audioBuffer_lock.Lock(); + size_t pos = g_audioBuffer.size(); + g_audioBuffer.resize(pos + got); + for(size_t p = 0; p < got; ++p) + g_audioBuffer[pos + p] = buff[p]; + g_audioBuffer_lock.Unlock(); + + const AudioOutputSpec &spec = obtained; + while(!stop && (g_audioBuffer.size() > static_cast<size_t>(spec.samples + (spec.freq * g_audioFormat.sampleOffset) * OurHeadRoomLength))) + { + audio_delay(1); + } + +# ifdef DEBUG_SONG_SWITCHING + if(kbhit()) + { + int code = getch(); + if(code == '\033' && kbhit()) + { + getch(); + switch(getch()) + { + case 'C': + // code for arrow right + songNumLoad++; + if(songNumLoad >= songsCount) + songNumLoad = songsCount; + adl_selectSongNum(myDevice, songNumLoad); + std::fprintf(stdout, "\rSwitching song to %d/%d... \r\n", songNumLoad, songsCount); + flushout(stdout); + break; + case 'D': + // code for arrow left + songNumLoad--; + if(songNumLoad < 0) + songNumLoad = 0; + adl_selectSongNum(myDevice, songNumLoad); + std::fprintf(stdout, "\rSwitching song to %d/%d... \r\n", songNumLoad, songsCount); + flushout(stdout); + break; + } + } + else if(code == 27) // Quit by ESC key + stop = 1; + } +# endif + +# ifdef DEBUG_SEEKING_TEST + if(delayBeforeSeek-- <= 0) + { + delayBeforeSeek = rand() % 50; + double seekTo = double((rand() % int(adl_totalTimeLength(myDevice)) - delayBeforeSeek - 1 )); + adl_positionSeek(myDevice, seekTo); + } +# endif + +# ifdef DEBUG_SONG_CHANGE + if(delayBeforeSongChange-- <= 0) + { + delayBeforeSongChange = rand() % 100; + adl_selectSongNum(myDevice, rand() % 10); + } +# endif + +# ifdef DEBUG_SONG_CHANGE_BY_HOOK + if(gotXmiTrigger) + { + gotXmiTrigger = false; + adl_selectSongNum(myDevice, (rand() % 10) + 1); + } +# endif + } + + s_timeCounter.clearLine(); + + audio_stop(); + audio_close(); + + return 0; +} +# endif // OUTPUT_WAVE_ONLY + +static int runWaveOutLoopLoop(ADL_MIDIPlayer *myDevice, const std::string &musPath, unsigned sampleRate) +{ + std::string wave_out = musPath + ".wav"; + std::fprintf(stdout, " - Recording WAV file %s...\n", wave_out.c_str()); + std::fprintf(stdout, "\n==========================================\n"); + flushout(stdout); + + if(wave_open(static_cast<long>(sampleRate), wave_out.c_str()) == 0) + { + wave_enable_stereo(); + short buff[4096]; + + while(!stop) + { + size_t got = static_cast<size_t>(adl_play(myDevice, 4096, buff)); + if(got <= 0) + break; + wave_write(buff, static_cast<long>(got)); + + s_timeCounter.printProgress(adl_positionTell(myDevice)); + } + + wave_close(); + s_timeCounter.clearLine(); + + if(stop) + std::fprintf(stdout, "Interrupted! Recorded WAV is incomplete, but playable!\n"); + else + std::fprintf(stdout, "Completed!\n"); + flushout(stdout); + } + else + { + adl_close(myDevice); + return 1; + } + + return 0; +} + +#else // HARDWARE_OPL3 +static void runDOSLoop(ADL_MIDIPlayer *myDevice) +{ + double tick_delay = 0.0; + + s_timeCounter.clearLineR(); + + while(!stop) + { + const double mindelay = 1.0 / s_timeCounter.newTimerFreq; + +# ifndef DEBUG_TRACE_ALL_EVENTS + s_timeCounter.printTime(adl_positionTell(myDevice)); +# endif + + s_timeCounter.waitDosTimer(); + + static unsigned long PrevTimer = BIOStimer; + const unsigned long CurTimer = BIOStimer; + const double eat_delay = (CurTimer - PrevTimer) / (double)s_timeCounter.newTimerFreq; + PrevTimer = CurTimer; + + tick_delay = adl_tickEvents(myDevice, eat_delay, mindelay); + + if(adl_atEnd(myDevice) && tick_delay <= 0) + stop = true; + + if(kbhit()) + { // Quit on ESC key! + int c = getch(); + if(c == 27) + stop = true; + } + } + + s_timeCounter.clearLine(); + + adl_panic(myDevice); //Shut up all sustaining notes +} +#endif // HARDWARE_OPL3 + + int main(int argc, char **argv) { std::fprintf(stdout, "==========================================\n" @@ -493,6 +1267,7 @@ int main(int argc, char **argv) " -ea Enable the auto-arpeggio\n" #ifndef HARDWARE_OPL3 " -fp Enables full-panning stereo support\n" + " --gain <value> Set the gaining factor (default 2.0)\n" " --emu-nuked Uses Nuked OPL3 v 1.8 emulator\n" " --emu-nuked7 Uses Nuked OPL3 v 1.7.4 emulator\n" " --emu-dosbox Uses DosBox 0.74 OPL3 emulator\n" @@ -519,36 +1294,28 @@ int main(int argc, char **argv) } unsigned int sampleRate = 44100; -#ifndef HARDWARE_OPL3 +#if !defined(HARDWARE_OPL3) && !defined(OUTPUT_WAVE_ONLY) //const unsigned MaxSamplesAtTime = 512; // 512=dbopl limitation // How long is SDL buffer, in seconds? // The smaller the value, the more often SDL_AudioCallBack() // is called. const double AudioBufferLength = 0.08; - // How much do WE buffer, in seconds? The smaller the value, - // the more prone to sound chopping we are. - const double OurHeadRoomLength = 0.1; - // The lag between visual content and audio content equals - // the sum of these two buffers. -# ifndef OUTPUT_WAVE_ONLY AudioOutputSpec spec; - AudioOutputSpec obtained; - spec.freq = sampleRate; spec.format = ADLMIDI_SampleType_S16; spec.is_msb = 0; spec.channels = 2; spec.samples = uint16_t((double)spec.freq * AudioBufferLength); -# endif //OUTPUT_WAVE_ONLY -#endif //HARDWARE_OPL3 +#endif // !HARDWARE_OPL3 && !OUTPUT_WAVE_ONLY -#ifdef HARDWARE_OPL3 - static unsigned newTimerFreq = 209; - unsigned timerPeriod = 0x1234DDul / newTimerFreq; +#if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) + bool hwSerial = false; + std::string serialName; + unsigned serialBaud = 115200; + unsigned serialProto = ADLMIDI_SerialProtocol_RetroWaveOPL3; #endif - ADL_MIDIPlayer *myDevice; //Initialize libADLMIDI and create the instance (you can initialize multiple of them!) @@ -648,12 +1415,57 @@ int main(int argc, char **argv) else if(!std::strcmp("--emu-java", argv[2])) emulator = ADLMIDI_EMU_JAVA; #endif +#if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) + else if(!std::strcmp("--serial", argv[2])) + { + if(argc <= 3) + { + printError("The option --serial requires an argument!\n"); + return 1; + } + had_option = true; + hwSerial = true; + serialName = argv[3]; + } + else if(!std::strcmp("--serial-baud", argv[2])) + { + if(argc <= 3) + { + printError("The option --serial-baud requires an argument!\n"); + return 1; + } + had_option = true; + serialBaud = std::strtol(argv[3], NULL, 10); + } + else if(!std::strcmp("--serial-proto", argv[2])) + { + if(argc <= 3) + { + printError("The option --serial-proto requires an argument!\n"); + return 1; + } + had_option = true; + serialProto = std::strtol(argv[3], NULL, 10); + } +#endif else if(!std::strcmp("-fp", argv[2])) adl_setSoftPanEnabled(myDevice, 1); else if(!std::strcmp("-mb", argv[2])) multibankFromEnbededTest = true; else if(!std::strcmp("-s", argv[2])) adl_setScaleModulators(myDevice, 1);//Turn on modulators scaling by volume +#ifndef ADLMIDI_HW_OPL + else if(!std::strcmp("--gain", argv[2])) + { + if(argc <= 3) + { + printError("The option --gain requires an argument!\n"); + return 1; + } + had_option = true; + g_gaining = std::atof(argv[3]); + } +#endif // HARDWARE_OPL3 #ifdef HARDWARE_OPL3 else if(!std::strcmp("--time-freq", argv[2])) @@ -663,14 +1475,15 @@ int main(int argc, char **argv) printError("The option --time-freq requires an argument!\n"); return 1; } - newTimerFreq = std::strtoul(argv[3], NULL, 0); - if(newTimerFreq == 0) + + unsigned timerFreq = std::strtoul(argv[3], NULL, 0); + if(timerFreq == 0) { printError("The option --time-freq requires a non-zero integer argument!\n"); return 1; } - timerPeriod = 0x1234DDul / newTimerFreq; + s_timeCounter.setDosTimerHZ(timerFreq); had_option = true; } @@ -763,6 +1576,11 @@ int main(int argc, char **argv) adl_setRawEventHook(myDevice, debugPrintEvent, NULL); #endif +#if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) + if(hwSerial) + adl_switchSerialHW(myDevice, serialName.c_str(), serialBaud, serialProto); + else +#endif #ifndef HARDWARE_OPL3 adl_switchEmulator(myDevice, emulator); #endif @@ -770,65 +1588,15 @@ int main(int argc, char **argv) std::fprintf(stdout, " - Library version %s\n", adl_linkedLibraryVersion()); #ifdef HARDWARE_OPL3 std::fprintf(stdout, " - Hardware OPL3 chip in use\n"); +#elif defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) + if(hwSerial) + std::fprintf(stdout, " - %s [device %s] in use\n", adl_chipEmulatorName(myDevice), serialName.c_str()); + else + std::fprintf(stdout, " - %s Emulator in use\n", adl_chipEmulatorName(myDevice)); #else std::fprintf(stdout, " - %s Emulator in use\n", adl_chipEmulatorName(myDevice)); #endif -#if !defined(HARDWARE_OPL3) && !defined(OUTPUT_WAVE_ONLY) - if(!recordWave) - { - // Set up SDL - if(audio_init(&spec, &obtained, SDL_AudioCallbackX) < 0) - { - std::fprintf(stderr, "\nERROR: Couldn't open audio: %s\n\n", audio_get_error()); - adl_close(myDevice); - return 1; - } - - if(spec.samples != obtained.samples) - { - std::fprintf(stderr, " - Audio wanted (format=%s,samples=%u,rate=%u,channels=%u);\n" - " - Audio obtained (format=%s,samples=%u,rate=%u,channels=%u)\n", - audio_format_to_str(spec.format, spec.is_msb), spec.samples, spec.freq, spec.channels, - audio_format_to_str(obtained.format, obtained.is_msb), obtained.samples, obtained.freq, obtained.channels); - } - - switch(obtained.format) - { - case ADLMIDI_SampleType_S8: - g_audioFormat.type = ADLMIDI_SampleType_S8; - g_audioFormat.containerSize = sizeof(int8_t); - g_audioFormat.sampleOffset = sizeof(int8_t) * 2; - break; - case ADLMIDI_SampleType_U8: - g_audioFormat.type = ADLMIDI_SampleType_U8; - g_audioFormat.containerSize = sizeof(uint8_t); - g_audioFormat.sampleOffset = sizeof(uint8_t) * 2; - break; - case ADLMIDI_SampleType_S16: - g_audioFormat.type = ADLMIDI_SampleType_S16; - g_audioFormat.containerSize = sizeof(int16_t); - g_audioFormat.sampleOffset = sizeof(int16_t) * 2; - break; - case ADLMIDI_SampleType_U16: - g_audioFormat.type = ADLMIDI_SampleType_U16; - g_audioFormat.containerSize = sizeof(uint16_t); - g_audioFormat.sampleOffset = sizeof(uint16_t) * 2; - break; - case ADLMIDI_SampleType_S32: - g_audioFormat.type = ADLMIDI_SampleType_S32; - g_audioFormat.containerSize = sizeof(int32_t); - g_audioFormat.sampleOffset = sizeof(int32_t) * 2; - break; - case ADLMIDI_SampleType_F32: - g_audioFormat.type = ADLMIDI_SampleType_F32; - g_audioFormat.containerSize = sizeof(float); - g_audioFormat.sampleOffset = sizeof(float) * 2; - break; - } - } -#endif - if(argc >= 3) { if(is_number(argv[2])) @@ -837,7 +1605,7 @@ int main(int argc, char **argv) //Choose one of embedded banks if(adl_setBank(myDevice, bankno) != 0) { - printError(adl_errorInfo(myDevice)); + printError(adl_errorInfo(myDevice), "Can't set an embedded bank"); adl_close(myDevice); return 1; } @@ -854,7 +1622,7 @@ int main(int argc, char **argv) { std::fprintf(stdout, "FAILED!\n"); flushout(stdout); - printError(adl_errorInfo(myDevice)); + printError(adl_errorInfo(myDevice), "Can't open a custom bank file"); adl_close(myDevice); return 1; } @@ -881,14 +1649,14 @@ int main(int argc, char **argv) ADL_Bank bank; if(adl_getBank(myDevice, &id[i], ADLMIDI_Bank_Create, &bank) < 0) { - printError(adl_errorInfo(myDevice)); + printError(adl_errorInfo(myDevice), "Can't get an embedded bank"); adl_close(myDevice); return 1; } if(adl_loadEmbeddedBank(myDevice, &bank, banks[i]) < 0) { - printError(adl_errorInfo(myDevice)); + printError(adl_errorInfo(myDevice), "Can't load an embedded bank"); adl_close(myDevice); return 1; } @@ -902,10 +1670,15 @@ int main(int argc, char **argv) if(argc >= 4) numOfChips = std::atoi(argv[3]); +#if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) + if(hwSerial) + numOfChips = 1; +#endif + //Set count of concurrent emulated chips count to excite channels limit of one chip if(adl_setNumChips(myDevice, numOfChips) != 0) { - printError(adl_errorInfo(myDevice)); + printError(adl_errorInfo(myDevice), "Can't set number of chips"); adl_close(myDevice); return 1; } @@ -922,7 +1695,7 @@ int main(int argc, char **argv) //Set total count of 4-operator channels between all emulated chips if(adl_setNumFourOpsChn(myDevice, std::atoi(argv[4])) != 0) { - printError(adl_errorInfo(myDevice)); + printError(adl_errorInfo(myDevice), "Can't set number of 4-op channels"); adl_close(myDevice); return 1; } @@ -933,6 +1706,7 @@ int main(int argc, char **argv) adl_setLoopEndHook(myDevice, loopEndCallback, NULL); adl_setLoopHooksOnly(myDevice, 1); #endif + if(songNumLoad >= 0) adl_selectSongNum(myDevice, songNumLoad); @@ -946,7 +1720,8 @@ int main(int argc, char **argv) //Open MIDI file to play if(adl_openFile(myDevice, musPath.c_str()) != 0) { - printError(adl_errorInfo(myDevice)); + printError(adl_errorInfo(myDevice), "Can't open MIDI file"); + adl_close(myDevice); return 2; } @@ -956,6 +1731,15 @@ int main(int argc, char **argv) std::fprintf(stdout, " - Volume model: %s\n", volume_model_to_str(adl_getVolumeRangeModel(myDevice))); std::fprintf(stdout, " - Channel allocation mode: %s\n", chanalloc_to_str(adl_getChannelAllocMode(myDevice))); +#ifndef HARDWARE_OPL3 +# ifdef ADLMIDI_ENABLE_HW_SERIAL + if(!hwSerial) +# endif + { + std::fprintf(stdout, " - Gain level: %g\n", g_gaining); + } +#endif + int songsCount = adl_getSongsCount(myDevice); if(songNumLoad >= 0) std::fprintf(stdout, " - Attempt to load song number: %d / %d\n", songNumLoad, songsCount); @@ -996,242 +1780,46 @@ int main(int argc, char **argv) # endif #else // HARDWARE_OPL3 - -# ifdef __DJGPP__ - //disable(); - errno = 0; - __dpmi_yield(); - int haveYield = errno ? 0 : 1; - - if(!haveYield) - std::fprintf(stdout, " - [DOS] dmpi_yield failed, using hlt\n"); - - outportb(0x43, 0x34); - outportb(0x40, timerPeriod & 0xFF); - outportb(0x40, timerPeriod >> 8); - std::fprintf(stdout, " - [DOS] Running clock with %d hz\n", newTimerFreq); - //enable(); -# endif//__DJGPP__ - -# ifdef __WATCOMC__ - std::fprintf(stdout, " - Initializing BIOS timer...\n"); - flushout(stdout); //disable(); - outp(0x43, 0x34); - outp(0x40, TimerPeriod & 0xFF); - outp(0x40, TimerPeriod >> 8); + s_timeCounter.initDosTimer(); + s_timeCounter.flushDosTimer(); //enable(); - std::fprintf(stdout, " - Ok!\n"); - flushout(stdout); -# endif//__WATCOMC__ - - unsigned long BIOStimer_begin = BIOStimer; - double tick_delay = 0.0; #endif//HARDWARE_OPL3 - double total = adl_totalTimeLength(myDevice); + s_timeCounter.setTotal(adl_totalTimeLength(myDevice)); #ifndef OUTPUT_WAVE_ONLY - double loopStart = adl_loopStartTime(myDevice); - double loopEnd = adl_loopEndTime(myDevice); - char totalHMS[25]; - char loopStartHMS[25]; - char loopEndHMS[25]; - secondsToHMSM(total, totalHMS, 25); - if(loopStart >= 0.0 && loopEnd >= 0.0) - { - secondsToHMSM(loopStart, loopStartHMS, 25); - secondsToHMSM(loopEnd, loopEndHMS, 25); - } + s_timeCounter.setLoop(adl_loopStartTime(myDevice), adl_loopEndTime(myDevice)); # ifndef HARDWARE_OPL3 if(!recordWave) # endif { std::fprintf(stdout, " - Loop is turned %s\n", loopEnabled ? "ON" : "OFF"); - if(loopStart >= 0.0 && loopEnd >= 0.0) - std::fprintf(stdout, " - Has loop points: %s ... %s\n", loopStartHMS, loopEndHMS); + if(s_timeCounter.hasLoop) + std::fprintf(stdout, " - Has loop points: %s ... %s\n", s_timeCounter.loopStartHMS, s_timeCounter.loopEndHMS); std::fprintf(stdout, "\n==========================================\n"); flushout(stdout); # ifndef HARDWARE_OPL3 - audio_start(); -# endif - -# ifdef DEBUG_SONG_CHANGE - int delayBeforeSongChange = 50; - std::fprintf(stdout, "DEBUG: === Random song set test is active! ===\n"); - flushout(stdout); -# endif - -# ifdef DEBUG_SEEKING_TEST - int delayBeforeSeek = 50; - std::fprintf(stdout, "DEBUG: === Random position set test is active! ===\n"); - flushout(stdout); -# endif - -# ifndef HARDWARE_OPL3 - uint8_t buff[16384]; -# endif - char posHMS[25]; - uint64_t milliseconds_prev = ~0u; - int printsCounter = 0; - int printsCounterPeriod = 1; -# ifdef HARDWARE_OPL3 - printsCounterPeriod = 500; -# endif - - std::fprintf(stdout, " \r"); - - while(!stop) - { -# ifndef HARDWARE_OPL3 - size_t got = (size_t)adl_playFormat(myDevice, 4096, - buff, - buff + g_audioFormat.containerSize, - &g_audioFormat) * g_audioFormat.containerSize; - if(got <= 0) - break; -# endif - -# ifdef DEBUG_TRACE_ALL_CHANNELS - enum { TerminalColumns = 80 }; - char channelText[TerminalColumns + 1]; - char channelAttr[TerminalColumns + 1]; - adl_describeChannels(myDevice, channelText, channelAttr, sizeof(channelText)); - std::fprintf(stdout, "%*s\r", TerminalColumns, ""); // erase the line - std::fprintf(stdout, "%s\n", channelText); -# endif - -# ifndef DEBUG_TRACE_ALL_EVENTS - double time_pos = adl_positionTell(myDevice); - uint64_t milliseconds = static_cast<uint64_t>(time_pos * 1000.0); - - if(milliseconds != milliseconds_prev) - { - if(printsCounter >= printsCounterPeriod) - { - printsCounter = -1; - secondsToHMSM(time_pos, posHMS, 25); - std::fprintf(stdout, " \r"); - std::fprintf(stdout, "Time position: %s / %s\r", posHMS, totalHMS); - flushout(stdout); - milliseconds_prev = milliseconds; - } - printsCounter++; - } -# endif - -# ifndef HARDWARE_OPL3 - g_audioBuffer_lock.Lock(); - size_t pos = g_audioBuffer.size(); - g_audioBuffer.resize(pos + got); - for(size_t p = 0; p < got; ++p) - g_audioBuffer[pos + p] = buff[p]; - g_audioBuffer_lock.Unlock(); - - const AudioOutputSpec &spec = obtained; - while(!stop && (g_audioBuffer.size() > static_cast<size_t>(spec.samples + (spec.freq * g_audioFormat.sampleOffset) * OurHeadRoomLength))) - { - audio_delay(1); - } - -# ifdef DEBUG_SONG_SWITCHING - if(kbhit()) - { - int code = getch(); - if(code == '\033' && kbhit()) - { - getch(); - switch(getch()) - { - case 'C': - // code for arrow right - songNumLoad++; - if(songNumLoad >= songsCount) - songNumLoad = songsCount; - adl_selectSongNum(myDevice, songNumLoad); - std::fprintf(stdout, "\rSwitching song to %d/%d... \r\n", songNumLoad, songsCount); - flushout(stdout); - break; - case 'D': - // code for arrow left - songNumLoad--; - if(songNumLoad < 0) - songNumLoad = 0; - adl_selectSongNum(myDevice, songNumLoad); - std::fprintf(stdout, "\rSwitching song to %d/%d... \r\n", songNumLoad, songsCount); - flushout(stdout); - break; - } - } - else if(code == 27) // Quit by ESC key - stop = 1; - } -# endif - -# ifdef DEBUG_SEEKING_TEST - if(delayBeforeSeek-- <= 0) - { - delayBeforeSeek = rand() % 50; - double seekTo = double((rand() % int(adl_totalTimeLength(myDevice)) - delayBeforeSeek - 1 )); - adl_positionSeek(myDevice, seekTo); - } -# endif - -# ifdef DEBUG_SONG_CHANGE - if(delayBeforeSongChange-- <= 0) - { - delayBeforeSongChange = rand() % 100; - adl_selectSongNum(myDevice, rand() % 10); - } +# ifdef ADLMIDI_ENABLE_HW_SERIAL + if(hwSerial) + runHWSerialLoop(myDevice); + else # endif - -# ifdef DEBUG_SONG_CHANGE_BY_HOOK - if(gotXmiTrigger) + { + int ret = runAudioLoop(myDevice, spec); + if (ret != 0) { - gotXmiTrigger = false; - adl_selectSongNum(myDevice, (rand() % 10) + 1); - } -# endif - -# else//HARDWARE_OPL3 - const double mindelay = 1.0 / newTimerFreq; - - //__asm__ volatile("sti\nhlt"); - //usleep(10000); -# ifdef __DJGPP__ - if(haveYield) - __dpmi_yield(); - else - __asm__ volatile("hlt"); -# endif -# ifdef __WATCOMC__ - //dpmi_dos_yield(); - mch_delay((unsigned int)(tick_delay * 1000.0)); -# endif - static unsigned long PrevTimer = BIOStimer; - const unsigned long CurTimer = BIOStimer; - const double eat_delay = (CurTimer - PrevTimer) / (double)newTimerFreq; - PrevTimer = CurTimer; - tick_delay = adl_tickEvents(myDevice, eat_delay, mindelay); - if(adl_atEnd(myDevice) && tick_delay <= 0) - stop = true; - - if(kbhit()) - { // Quit on ESC key! - int c = getch(); - if(c == 27) - stop = true; + adl_close(myDevice); + return ret; } - -# endif//HARDWARE_OPL3 } - std::fprintf(stdout, " \n\n"); -# ifndef HARDWARE_OPL3 - audio_stop(); - audio_close(); +# else + runDOSLoop(myDevice); # endif + + s_timeCounter.clearLine(); } #endif //OUTPUT_WAVE_ONLY @@ -1241,77 +1829,20 @@ int main(int argc, char **argv) else # endif //OUTPUT_WAVE_ONLY { - std::string wave_out = musPath + ".wav"; - std::fprintf(stdout, " - Recording WAV file %s...\n", wave_out.c_str()); - std::fprintf(stdout, "\n==========================================\n"); - flushout(stdout); - - if(wave_open(static_cast<long>(sampleRate), wave_out.c_str()) == 0) - { - wave_enable_stereo(); - short buff[4096]; - int complete_prev = -1; - while(!stop) - { - size_t got = static_cast<size_t>(adl_play(myDevice, 4096, buff)); - if(got <= 0) - break; - wave_write(buff, static_cast<long>(got)); - - int complete = static_cast<int>(std::floor(100.0 * adl_positionTell(myDevice) / total)); - flushout(stdout); - if(complete_prev != complete) - { - std::fprintf(stdout, " \r"); - std::fprintf(stdout, "Recording WAV... [%d%% completed]\r", complete); - std::fflush(stdout); - complete_prev = complete; - } - } - wave_close(); - std::fprintf(stdout, " \n\n"); - - if(stop) - std::fprintf(stdout, "Interrupted! Recorded WAV is incomplete, but playable!\n"); - else - std::fprintf(stdout, "Completed!\n"); - flushout(stdout); - } - else + int ret = runWaveOutLoopLoop(myDevice, musPath, sampleRate); + if(ret != 0) { adl_close(myDevice); - return 1; + return ret; } } #endif //HARDWARE_OPL3 #ifdef HARDWARE_OPL3 - -# ifdef __DJGPP__ - // Fix the skewed clock and reset BIOS tick rate - _farpokel(_dos_ds, 0x46C, BIOStimer_begin + - (BIOStimer - BIOStimer_begin) - * (0x1234DD / 65536.0) / newTimerFreq); - - //disable(); - outportb(0x43, 0x34); - outportb(0x40, 0); - outportb(0x40, 0); - //enable(); -# endif - -# ifdef __WATCOMC__ - outp(0x43, 0x34); - outp(0x40, 0); - outp(0x40, 0); -# endif - - adl_panic(myDevice); //Shut up all sustaining notes - + s_timeCounter.restoreDosTimer(); #endif adl_close(myDevice); return 0; } - diff --git a/utils/midiplay/utf8main.cmake b/utils/midiplay/utf8main.cmake new file mode 100644 index 0000000..1dcac78 --- /dev/null +++ b/utils/midiplay/utf8main.cmake @@ -0,0 +1,10 @@ +# message("Path to UTF8-Main is [${CMAKE_CURRENT_LIST_DIR}]") +include_directories(${CMAKE_CURRENT_LIST_DIR}/) + +set(UTF8MAIN_SRCS) + +if(WIN32) + list(APPEND UTF8MAIN_SRCS + ${CMAKE_CURRENT_LIST_DIR}/utf8main_win32.cpp + ) +endif() diff --git a/utils/midiplay/utf8main.h b/utils/midiplay/utf8main.h new file mode 100644 index 0000000..8cab2cc --- /dev/null +++ b/utils/midiplay/utf8main.h @@ -0,0 +1,41 @@ +/* + * utf8main - a small wrapper over entry points + * to provide the UTF8 encoded main function + * + * Copyright (c) 2017-2023 Vitaly Novichkov <admin@wohlnet.ru> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef UTF8MAIN_H +#define UTF8MAIN_H + +#ifndef PGE_ENGINE//Don't use in PGE Engine where SDL Main is already in use +//Windows platform +#ifdef _WIN32 +//Avoid Qt to steal Main function +#if defined(QT_NEEDS_QMAIN) +#undef main +#endif +//Define custom UTF8 main function which will convert command line arguments into UTF8 and will pass them +#define main UTF8_Main +#endif + +#endif + +#endif // UTF8MAIN_H diff --git a/utils/midiplay/utf8main_win32.cpp b/utils/midiplay/utf8main_win32.cpp new file mode 100644 index 0000000..1159b97 --- /dev/null +++ b/utils/midiplay/utf8main_win32.cpp @@ -0,0 +1,95 @@ +/* + * utf8main - a small wrapper over entry points + * to provide the UTF8 encoded main function + * + * Copyright (c) 2017-2023 Vitaly Novichkov <admin@wohlnet.ru> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifdef _WIN32 +#include <windows.h> +#include <shellapi.h> +#include <string> +#include <vector> + +#ifdef PGE_ENGINE +# include <SDL2/SDL_main.h> +#else +# include "utf8main.h" +#endif + +#ifndef _In_ +# define _In_ +#endif +#ifndef _In_opt_ +# define _In_opt_ +#endif + +extern int main(int argc, char *argv[]); + +static void buildUtf8Args(std::vector<std::string> &utf8_args) +{ + //Get raw UTF-16 command line + wchar_t *cmdLineW = GetCommandLineW(); + int argc = 0; + //Convert it into array of strings + wchar_t **argvW = CommandLineToArgvW(cmdLineW, &argc); + + utf8_args.reserve(argc); + //Convert every argument into UTF-8 + for(int i = 0; i < argc; i++) + { + wchar_t *argW = argvW[i]; + size_t argWlen = wcslen(argW); + std::string arg; + arg.resize(argWlen * 2); + size_t newLen = WideCharToMultiByte(CP_UTF8, 0, argW, static_cast<int>(argWlen), &arg[0], static_cast<int>(arg.size()), 0, 0); + arg.resize(newLen); + utf8_args.push_back(arg); + } +} + +#ifdef WIN32_CONSOLE +#undef main +int main() +# define main UTF8_Main +#else +int WINAPI WinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PSTR, _In_ int) +#endif +{ + //! Storage of UTF8-encoded command line arguments + std::vector<std::string> g_utf8_args; + //! Storage of the pointers to begin of every argument + std::vector<char *> g_utf8_argvV; + + buildUtf8Args(g_utf8_args); + +#ifdef UTF8Main_Debug + printf("UTF8 ARGS RAN!\n"); + fflush(stdout); +#endif + + size_t argc = g_utf8_args.size(); + g_utf8_argvV.reserve(argc); + for(size_t i = 0; i < argc; i++) + g_utf8_argvV.push_back(&g_utf8_args[i][0]); + + return main((int)argc, g_utf8_argvV.data()); +} +#endif diff --git a/utils/vlc_codec/libadlmidi.c b/utils/vlc_codec/libadlmidi.c index fe13237..be8664e 100644 --- a/utils/vlc_codec/libadlmidi.c +++ b/utils/vlc_codec/libadlmidi.c @@ -89,6 +89,36 @@ #define ENABLE_AUTO_ARPEGGIO_LONGTEXT N_( \ "Enables an automatical arpeggio to keep chords playing when there is no enough free physical voices of chips.") + +#ifdef ADLMIDI_ENABLE_HW_SERIAL + +#define SERIAL_ENABLE_TEXT N_("Enable Serial hardware mode") +#define SERIAL_ENABLE_LONGTEXT N_( \ + "Use the hardware OPL3 chip via Serial port.") + +#define SERIAL_NAME_TEXT N_("Serial device name") +#define SERIAL_NAME_LONGTEXT N_( \ + "Name of the Serial device to use.") + +#define SERIAL_BAUD_TEXT N_("Serial baud speed") +#define SERIAL_BAUD_LONGTEXT N_( \ + "The baud speed value of the Serial device.") + +#define SERIAL_PROTO_TEXT N_("Serial protocol") +#define SERIAL_PROTO_LONGTEXT N_( \ + "The protocol to use the Serial device.") + +static const int serial_protos_values[] = { 1, 2, 3 }; +static const char * const serial_protos_descriptions[] = + { + N_("Arduino OPL2"), + N_("NukeYkt OPL3"), + N_("RetroWave OPL3"), + NULL +}; +#endif // ADLMIDI_ENABLE_HW_SERIAL + + static const int volume_models_values[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; static const char * const volume_models_descriptions[] = { @@ -192,6 +222,17 @@ vlc_module_begin () add_bool( CONFIG_PREFIX "full-panning", true, FULL_PANNING_TEXT, FULL_PANNING_LONGTEXT, true ) +#ifdef ADLMIDI_ENABLE_HW_SERIAL + add_bool(CONFIG_PREFIX "serial-enable", false, SERIAL_ENABLE_TEXT, SERIAL_ENABLE_LONGTEXT, true) + add_string(CONFIG_PREFIX "serial-name", "", SERIAL_NAME_TEXT, SERIAL_NAME_LONGTEXT, true) + add_integer(CONFIG_PREFIX "serial-baud", 115200, SERIAL_BAUD_TEXT, SERIAL_BAUD_LONGTEXT, true) + change_integer_range (9600, 115200) + + add_integer (CONFIG_PREFIX "serial-proto", 3, SERIAL_PROTO_TEXT, SERIAL_PROTO_LONGTEXT, false) + change_integer_list( serial_protos_values, serial_protos_descriptions ) +#endif + + vlc_module_end () @@ -201,6 +242,7 @@ struct decoder_sys_t int sample_rate; int soundfont; date_t end_date; + int is_serial; }; static const struct ADLMIDI_AudioFormat g_output_format = @@ -234,15 +276,25 @@ static int Open (vlc_object_t *p_this) p_sys->sample_rate = var_InheritInteger (p_this, CONFIG_PREFIX "sample-rate"); p_sys->synth = adl_init( p_sys->sample_rate ); - adl_switchEmulator(p_sys->synth, var_InheritInteger(p_this, CONFIG_PREFIX "emulator-type")); - - adl_setNumChips(p_sys->synth, var_InheritInteger(p_this, CONFIG_PREFIX "emulated-chips")); + if(var_InheritBool(p_this, CONFIG_PREFIX "serial-enable")) + { + adl_switchSerialHW(p_sys->synth, + var_InheritString(p_this, CONFIG_PREFIX "serial-name"), + var_InheritInteger(p_this, CONFIG_PREFIX "serial-baud"), + var_InheritInteger(p_this, CONFIG_PREFIX "serial-proto") + ); + p_sys->is_serial = 1; + } + else + { + adl_switchEmulator(p_sys->synth, var_InheritInteger(p_this, CONFIG_PREFIX "emulator-type")); + adl_setNumChips(p_sys->synth, var_InheritInteger(p_this, CONFIG_PREFIX "emulated-chips")); + adl_setSoftPanEnabled(p_sys->synth, var_InheritBool(p_this, CONFIG_PREFIX "full-panning")); + p_sys->is_serial = 0; + } adl_setVolumeRangeModel(p_sys->synth, var_InheritInteger(p_this, CONFIG_PREFIX "volume-model")); - - adl_setChannelAllocMode(p_sys->synth, var_InheritInteger(p_this, CONFIG_PREFIX "channel-allocation")); - - adl_setSoftPanEnabled(p_sys->synth, var_InheritBool(p_this, CONFIG_PREFIX "full-panning")); + adl_setChannelAllocMode(p_sys->synth, var_InheritInteger(p_this, CONFIG_PREFIX "channel-allocation")); adl_setFullRangeBrightness(p_sys->synth, var_InheritBool(p_this, CONFIG_PREFIX "full-range-brightness")); @@ -290,6 +342,9 @@ static void Close (vlc_object_t *p_this) { decoder_sys_t *p_sys = ((decoder_t *)p_this)->p_sys; + if(p_sys->is_serial) + adl_panic(p_sys->synth); + adl_close(p_sys->synth); free (p_sys); } @@ -417,8 +472,11 @@ static block_t *DecodeBlock (decoder_t *p_dec, block_t **pp_block) break; } - unsigned samples = - (p_block->i_pts - date_Get (&p_sys->end_date)) * 441 / 10000; + unsigned samples = (p_block->i_pts - date_Get (&p_sys->end_date)) * 441 / 10000; +#ifdef ADLMIDI_ENABLE_HW_SERIAL + double delay = ((p_block->i_pts - date_Get (&p_sys->end_date)) / 1000000.0) / 2.0; +#endif + if (samples == 0) goto drop; @@ -432,13 +490,27 @@ static block_t *DecodeBlock (decoder_t *p_dec, block_t **pp_block) goto drop; p_out->i_pts = date_Get (&p_sys->end_date ); - samples = adl_generateFormat(p_sys->synth, (int)samples * 2, - (ADL_UInt8*)p_out->p_buffer, - (ADL_UInt8*)(p_out->p_buffer + g_output_format.containerSize), - &g_output_format); - for (it = 0; it < samples; ++it) - ((float*)p_out->p_buffer)[it] *= 2.0f; +#ifdef ADLMIDI_ENABLE_HW_SERIAL + if(p_sys->is_serial) + { + if(delay > 0.0) + adl_tickIterators(p_sys->synth, delay); + + for (it = 0; it < samples * 2; ++it) + ((float*)p_out->p_buffer)[it] = 0.0f; + } + else +#endif + { + samples = adl_generateFormat(p_sys->synth, (int)samples * 2, + (ADL_UInt8*)p_out->p_buffer, + (ADL_UInt8*)(p_out->p_buffer + g_output_format.containerSize), + &g_output_format); + + for (it = 0; it < samples; ++it) + ((float*)p_out->p_buffer)[it] *= 2.0f; + } samples /= 2; p_out->i_length = date_Increment (&p_sys->end_date, samples) - p_out->i_pts; |