diff options
author | Wohlstand <admin@wohlnet.ru> | 2025-03-29 00:44:52 +0300 |
---|---|---|
committer | Wohlstand <admin@wohlnet.ru> | 2025-03-29 00:44:52 +0300 |
commit | e92a4d7285197ab2868aa36f975d73ceee915bea (patch) | |
tree | b5e3af6e6c58145a62ec6394a40e9e65d26a5fcf | |
parent | 7afda0483ab0af9db624052321b42c3d2a245e75 (diff) | |
download | libADLMIDI-e92a4d7285197ab2868aa36f975d73ceee915bea.tar.gz libADLMIDI-e92a4d7285197ab2868aa36f975d73ceee915bea.tar.bz2 libADLMIDI-e92a4d7285197ab2868aa36f975d73ceee915bea.zip |
Refactored DOS support
-rw-r--r-- | CMakeLists.txt | 10 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | include/adlmidi.h | 21 | ||||
-rw-r--r-- | src/adlmidi.cpp | 39 | ||||
-rw-r--r-- | src/adlmidi_midiplay.cpp | 4 | ||||
-rw-r--r-- | src/adlmidi_opl3.cpp | 127 | ||||
-rw-r--r-- | src/adlmidi_opl3.hpp | 49 | ||||
-rw-r--r-- | src/adlmidi_private.hpp | 11 | ||||
-rw-r--r-- | src/chips/dos_hw_opl.cpp | 163 | ||||
-rw-r--r-- | src/chips/dos_hw_opl.h | 48 | ||||
-rw-r--r-- | utils/midiplay/adlmidiplay.cpp | 972 |
11 files changed, 989 insertions, 458 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 06896b7..2daa858 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,7 +278,15 @@ function(handle_options targetLib) target_compile_definitions(${targetLib} PUBLIC BWMIDI_DISABLE_XMI_SUPPORT) endif() - if(NOT ADLMIDI_DOS) + if(ADLMIDI_DOS) + set(HAS_EMULATOR TRUE) + target_sources(${targetLib} PRIVATE + ${libADLMIDI_SOURCE_DIR}/src/chips/dos_hw_opl.cpp + ${libADLMIDI_SOURCE_DIR}/src/chips/dos_hw_opl.h + ) + target_compile_definitions(${targetLib} PRIVATE ENABLE_HW_OPL_DOS) + target_compile_definitions(${targetLib} PUBLIC ADLMIDI_ENABLE_HW_DOS) + else() if(USE_DOSBOX_EMULATOR) set(HAS_EMULATOR TRUE) target_sources(${targetLib} PRIVATE @@ -194,6 +194,9 @@ To build that example you will need to have installed SDL2 library. * Added ESFMu emulator for the future ESFM support (Currently used as one another OPL3 emulator and without panned stereo support yet). * Added YMFM emulator support (OPL3 and OPL2). * Added support for OPL2 mode when some emulators enabled. + * Added ability to change the hardware address at the DOS version of MIDI player. + * Added an ability to manually specify the chip type (OPL2 or OPL3) at the DOS version of MIDI player. + * Added an automatical detection of OPL2 or OPL3 chip depending on the BLASTER environment variable's value at the DOS version of MIDI player. ## 1.5.1 2022-10-31 * Added an ability to disable the automatical arpeggio diff --git a/include/adlmidi.h b/include/adlmidi.h index 13e39d9..27239e0 100644 --- a/include/adlmidi.h +++ b/include/adlmidi.h @@ -769,6 +769,27 @@ extern ADLMIDI_DECLSPEC int adl_switchSerialHW(struct ADL_MIDIPlayer *device, unsigned baud, unsigned protocol); + +/** + * \brief The list of possible chip types for DOS hardware interface + */ +enum ADL_DOS_ChipType +{ + ADLMIDI_DOS_ChipAuto = 0, + ADLMIDI_DOS_ChipOPL2, + ADLMIDI_DOS_ChipOPL3 +}; + +/** + * @brief Specify the chip type and the base address before initialization. + * This function has no effect if library didn't built for DOS with hardware OPL mode + * Important: Run this BEFORE creating the instance of the library. + * @param chipType + * @param baseAddress + * @return 0 on success, <0 when any error has occurred + */ +extern ADLMIDI_DECLSPEC int adl_switchDOSHW(int chipType, ADL_UInt16 baseAddress); + /** * @brief Set 4-bit device identifier. Used by the SysEx processor. * @param device Instance of the library diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 7fbf134..b81a342 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -24,11 +24,12 @@ #include "adlmidi_midiplay.hpp" #include "adlmidi_opl3.hpp" #include "adlmidi_private.hpp" -#ifndef ADLMIDI_HW_OPL #include "chips/opl_chip_base.h" -#endif #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -#include "midi_sequencer.hpp" +# include "midi_sequencer.hpp" +#endif +#ifdef ENABLE_HW_OPL_DOS +# include "chips/dos_hw_opl.h" #endif #if defined(_MSC_VER) && _MSC_VER < 1900 @@ -138,8 +139,7 @@ ADLMIDI_EXPORT int adl_setNumChips(ADL_MIDIPlayer *device, int numChips) #endif #ifdef ADLMIDI_HW_OPL - ADL_UNUSED(numChips); - play->m_setup.numChips = 1; + play->m_setup.numChips = numChips > 2 ? 1 : static_cast<unsigned int>(numChips); #else play->m_setup.numChips = static_cast<unsigned int>(numChips); #endif @@ -835,15 +835,11 @@ ADLMIDI_EXPORT const char *adl_chipEmulatorName(struct ADL_MIDIPlayer *device) { if(device) { -#ifndef ADLMIDI_HW_OPL MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth &synth = *play->m_synth; if(!synth.m_chips.empty()) return synth.m_chips[0]->emulatorName(); -#else - return "Hardware OPL3 chip on 0x330"; -#endif } return "Unknown"; } @@ -910,6 +906,31 @@ ADLMIDI_EXPORT int adl_switchSerialHW(struct ADL_MIDIPlayer *device, return -1; } +int adl_switchDOSHW(int chipType, ADL_UInt16 baseAddress) +{ +#ifdef ENABLE_HW_OPL_DOS + if(baseAddress > 0) + DOS_HW_OPL::setOplAddress(baseAddress); + + switch(chipType) + { + case ADLMIDI_DOS_ChipAuto: + break; + + case ADLMIDI_DOS_ChipOPL2: + DOS_HW_OPL::setChipType(OPLChipBase::CHIPTYPE_OPL2); + break; + case ADLMIDI_DOS_ChipOPL3: + DOS_HW_OPL::setChipType(OPLChipBase::CHIPTYPE_OPL3); + break; + } +#else + (void)chipType; + (void)baseAddress; +#endif + return -1; +} + ADLMIDI_EXPORT const char *adl_linkedLibraryVersion() { #if !defined(ADLMIDI_ENABLE_HQ_RESAMPLER) diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 0bc0191..9db4b67 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -89,7 +89,11 @@ MIDIplay::MIDIplay(unsigned long sampleRate): m_setup.bankId = 0; m_setup.numFourOps = -1; +#ifdef ADLMIDI_ENABLE_HW_DOS + m_setup.numChips = 1; +#else m_setup.numChips = 2; +#endif m_setup.deepTremoloMode = -1; m_setup.deepVibratoMode = -1; m_setup.rhythmMode = -1; diff --git a/src/adlmidi_opl3.cpp b/src/adlmidi_opl3.cpp index c612637..97760cf 100644 --- a/src/adlmidi_opl3.cpp +++ b/src/adlmidi_opl3.cpp @@ -26,12 +26,9 @@ #include <stdlib.h> #include <cassert> -#ifndef DISABLE_EMBEDDED_BANKS -#include "wopl/wopl_file.h" -#endif -#ifdef ADLMIDI_HW_OPL -static const unsigned OPLBase = 0x388; +#ifdef ENABLE_HW_OPL_DOS +# include "chips/dos_hw_opl.h" #else # if defined(ADLMIDI_DISABLE_NUKED_EMULATOR) && \ defined(ADLMIDI_DISABLE_DOSBOX_EMULATOR) && \ @@ -84,7 +81,7 @@ static const unsigned OPLBase = 0x388; #endif static const unsigned adl_emulatorSupport = 0 -#ifndef ADLMIDI_HW_OPL +#ifndef ENABLE_HW_OPL_DOS # ifndef ADLMIDI_DISABLE_NUKED_EMULATOR | (1u << ADLMIDI_EMU_NUKED) | (1u << ADLMIDI_EMU_NUKED_174) # endif @@ -936,7 +933,9 @@ OPL3::OPL3() : OPL3::~OPL3() { -#ifdef ADLMIDI_HW_OPL + m_curState.clear(); + +#ifdef ENABLE_HW_OPL_DOS silenceAll(); writeRegI(0, 0x0BD, 0); writeRegI(0, 0x104, 0); @@ -1004,48 +1003,17 @@ void OPL3::setEmbeddedBank(uint32_t bank) void OPL3::writeReg(size_t chip, uint16_t address, uint8_t value) { -#ifdef ADLMIDI_HW_OPL - ADL_UNUSED(chip); - unsigned o = address >> 8; - unsigned port = OPLBase + o * 2; - -# ifdef __DJGPP__ - outportb(port, address); - for(unsigned c = 0; c < 6; ++c) inportb(port); - outportb(port + 1, value); - for(unsigned c = 0; c < 35; ++c) inportb(port); -# endif - -# ifdef __WATCOMC__ - outp(port, address); - for(uint16_t c = 0; c < 6; ++c) inp(port); - outp(port + 1, value); - for(uint16_t c = 0; c < 35; ++c) inp(port); -# endif//__WATCOMC__ - -#else//ADLMIDI_HW_OPL m_chips[chip]->writeReg(address, value); -#endif } void OPL3::writeRegI(size_t chip, uint32_t address, uint32_t value) { -#ifdef ADLMIDI_HW_OPL - writeReg(chip, static_cast<uint16_t>(address), static_cast<uint8_t>(value)); -#else//ADLMIDI_HW_OPL m_chips[chip]->writeReg(static_cast<uint16_t>(address), static_cast<uint8_t>(value)); -#endif } void OPL3::writePan(size_t chip, uint32_t address, uint32_t value) { -#ifndef ADLMIDI_HW_OPL m_chips[chip]->writePan(static_cast<uint16_t>(address), static_cast<uint8_t>(value)); -#else - ADL_UNUSED(chip); - ADL_UNUSED(address); - ADL_UNUSED(value); -#endif } @@ -1579,7 +1547,7 @@ void OPL3::setPan(size_t c, uint8_t value) if(g_channelsMapPan[cc] != 0xFFF) { -#ifndef ADLMIDI_HW_OPL +#ifndef ENABLE_HW_OPL_DOS if (m_softPanningSup && m_softPanning) { writePan(chip, g_channelsMapPan[cc], value); @@ -1595,7 +1563,7 @@ void OPL3::setPan(size_t c, uint8_t value) m_regC0[c] = panning; writePan(chip, g_channelsMapPan[cc], 64); writeRegI(chip, 0xC0 + g_channelsMapPan[cc], m_insCache[c].feedconn | panning); -#ifndef ADLMIDI_HW_OPL +#ifndef ENABLE_HW_OPL_DOS } #endif } @@ -1795,48 +1763,72 @@ ADLMIDI_VolumeModels OPL3::getVolumeScaleModel() } } -#ifndef ADLMIDI_HW_OPL void OPL3::clearChips() { for(size_t i = 0; i < m_chips.size(); i++) m_chips[i].reset(NULL); m_chips.clear(); } -#endif void OPL3::reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler) { -#ifndef ADLMIDI_HW_OPL - clearChips(); -#else - (void)emulator; - (void)PCM_RATE; -#endif + bool rebuild_needed = m_curState.cmp(emulator, m_numChips); + + if(rebuild_needed) + clearChips(); + #if !defined(ADLMIDI_AUDIO_TICK_HANDLER) (void)audioTickHandler; #endif - m_insCache.clear(); - m_keyBlockFNumCache.clear(); - m_regBD.clear(); -#ifndef ADLMIDI_HW_OPL - m_chips.resize(m_numChips, AdlMIDI_SPtr<OPLChipBase>()); -#endif + const struct OplTimbre defaultInsCache = { 0x1557403,0x005B381, 0x49,0x80, 0x4, +0 }; + + if(rebuild_needed) + { + m_insCache.clear(); + m_keyBlockFNumCache.clear(); + m_regBD.clear(); + m_regC0.clear(); + m_channelCategory.clear(); + m_chips.resize(m_numChips, AdlMIDI_SPtr<OPLChipBase>()); + } + else + { + adl_fill_vector<OplTimbre>(m_insCache, defaultInsCache); + adl_fill_vector<uint32_t>(m_channelCategory, 0); + adl_fill_vector<uint32_t>(m_keyBlockFNumCache, 0); + adl_fill_vector<uint32_t>(m_regBD, 0); + adl_fill_vector<uint8_t>(m_regC0, OPL_PANNING_BOTH); + } #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); - m_keyBlockFNumCache.resize(m_numChannels, 0); - m_regBD.resize(m_numChips, 0); - m_regC0.resize(m_numChips * m_numChannels, OPL_PANNING_BOTH); - m_channelCategory.resize(m_numChannels, 0); + if(rebuild_needed) + { + m_numChannels = m_numChips * NUM_OF_CHANNELS; + m_insCache.resize(m_numChannels, defaultInsCache); + m_channelCategory.resize(m_numChannels, 0); + m_keyBlockFNumCache.resize(m_numChannels, 0); + m_regBD.resize(m_numChips, 0); + m_regC0.resize(m_numChips * m_numChannels, OPL_PANNING_BOTH); + } - for(size_t i = 0; i < m_numChips; ++i) + if(!rebuild_needed) + { + bool newRate = m_curState.cmp_rate(PCM_RATE); + + for(size_t i = 0; i < m_numChips; ++i) + { + if(newRate) + m_chips[i]->setRate(PCM_RATE); + + initChip(i); + } + } + else for(size_t i = 0; i < m_numChips; ++i) { #ifdef ADLMIDI_ENABLE_HW_SERIAL if(emulator < 0) @@ -1849,8 +1841,11 @@ void OPL3::reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler) } #endif -#ifndef ADLMIDI_HW_OPL OPLChipBase *chip; +#ifdef ENABLE_HW_OPL_DOS + chip = new DOS_HW_OPL(); + +#else // ENABLE_HW_OPL_DOS switch(emulator) { default: @@ -1898,18 +1893,20 @@ void OPL3::reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler) break; #endif } +#endif // ENABLE_HW_OPL_DOS m_chips[i].reset(chip); chip->setChipId((uint32_t)i); chip->setRate((uint32_t)PCM_RATE); +#ifndef ENABLE_HW_OPL_DOS if(m_runAtPcmRate) chip->setRunningAtPcmRate(true); +#endif -# if defined(ADLMIDI_AUDIO_TICK_HANDLER) +# if defined(ADLMIDI_AUDIO_TICK_HANDLER) && !defined(ENABLE_HW_OPL_DOS) chip->setAudioTickHandlerInstance(audioTickHandler); # endif -#endif // ADLMIDI_HW_OPL initChip(i); } diff --git a/src/adlmidi_opl3.hpp b/src/adlmidi_opl3.hpp index 362b301..aee049f 100644 --- a/src/adlmidi_opl3.hpp +++ b/src/adlmidi_opl3.hpp @@ -60,10 +60,8 @@ public: uint32_t m_numChannels; //! Just a padding. Reserved. char _padding[4]; -#ifndef ADLMIDI_HW_OPL //! Running chip emulators std::vector<AdlMIDI_SPtr<OPLChipBase > > m_chips; -#endif private: //! Cached patch data, needed by Touch() @@ -90,6 +88,51 @@ private: //! Number channels per chip size_t m_perChipChannels; + /*! + * \brief Current state of the synth (if values matched to setup, chips and arrays won't be fully re-created) + */ + struct State + { + int emulator; + uint32_t numChips; + unsigned long pcm_rate; + + State() + { + clear(); + } + + void clear() + { + emulator = -2; + numChips = 0; + pcm_rate = 0; + } + + bool cmp_rate(unsigned long rate) + { + bool ret = pcm_rate != rate; + + if(ret) + pcm_rate = rate; + + return ret; + } + + bool cmp(int emu, uint32_t chips) + { + bool ret = emu != emulator || chips != numChips; + + if(ret) + { + emulator = emu; + numChips = chips; + } + + return ret; + } + } m_curState; + public: /** * @brief MIDI bank entry @@ -339,12 +382,10 @@ public: */ ADLMIDI_VolumeModels getVolumeScaleModel(); -#ifndef ADLMIDI_HW_OPL /** * @brief Clean up all running emulated chip instances */ void clearChips(); -#endif /** * @brief Reset chip properties and initialize them diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 0b1e92f..dcd0d03 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -60,9 +60,6 @@ typedef int32_t ssize_t; #endif #if defined(__DJGPP__) || (defined(__WATCOMC__) && (defined(__DOS__) || defined(__DOS4G__) || defined(__DOS4GNZ__))) -# ifndef ADLMIDI_HW_OPL -# define ADLMIDI_HW_OPL -# endif # include <conio.h> # ifdef __DJGPP__ # include <pc.h> @@ -157,7 +154,7 @@ class MIDIplay; #define ADL_UNUSED(x) (void)x -#ifdef ADLMIDI_HW_OPL +#ifdef ENABLE_HW_OPL_DOS #define ADL_MAX_CHIPS 1 #define ADL_MAX_CHIPS_STR "1" //Why not just "#MaxCards" ? Watcom fails to pass this with "syntax error" :-P #else @@ -214,6 +211,12 @@ inline int32_t adl_cvtU32(int32_t x) return (uint32_t)adl_cvtS32(x) - (uint32_t)INT32_MIN; } +template<typename T> +void adl_fill_vector(std::vector<T > &v, const T &value) +{ + for(typename std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) + *it = value; +} #if defined(ADLMIDI_AUDIO_TICK_HANDLER) extern void adl_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate); diff --git a/src/chips/dos_hw_opl.cpp b/src/chips/dos_hw_opl.cpp new file mode 100644 index 0000000..a9f4765 --- /dev/null +++ b/src/chips/dos_hw_opl.cpp @@ -0,0 +1,163 @@ +/* + * Interfaces over Yamaha OPL3 (YMF262) chip emulators + * + * Copyright (c) 2017-2025 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 + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#ifdef __DJGPP__ +# include <pc.h> +#endif + +#include "dos_hw_opl.h" + +static uint16_t s_OPLBase[2] = {0x388, 0x38A}; +static char s_devName[80] = {0}; +static OPLChipBase::ChipType s_type = OPLChipBase::CHIPTYPE_OPL3; +static bool s_detected = false; + +static void s_updateDevName() +{ + const char *oplName = (s_type == OPLChipBase::CHIPTYPE_OPL3) ? "OPL3" : "OPL2"; + memset(s_devName, 0, sizeof(s_devName)); + snprintf(s_devName, 80, "%s at 0x%03X", oplName, s_OPLBase[0]); +} + +static void s_detect() +{ + if(s_detected) + return; + + const char *blaster = getenv("BLASTER"); + if(blaster) + { + size_t len = strlen(blaster); + for(size_t i = 0; i < len - 1; ++i) + { + if(blaster[i] == 'T') + { + switch(blaster[i + 1]) + { + case '1': + case '2': + s_type = OPLChipBase::CHIPTYPE_OPL2; + break; + case '3': + case '4': + case '6': + s_type = OPLChipBase::CHIPTYPE_OPL3; + break; + default: + s_type = OPLChipBase::CHIPTYPE_OPL2; + break; + } + + // printf("-- Detected BLASTER T%c\n", blaster[i + 1]); + + break; + } + } + } + else + s_type = OPLChipBase::CHIPTYPE_OPL2; + + s_detected = true; +} + +DOS_HW_OPL::DOS_HW_OPL() +{ + s_detect(); + s_updateDevName(); +} + +void DOS_HW_OPL::setOplAddress(uint16_t address) +{ + s_OPLBase[0] = address; + s_OPLBase[1] = address + 2; + s_updateDevName(); +} + +void DOS_HW_OPL::setChipType(ChipType type) +{ + s_type = type; + s_detected = true; // Assignd manually, no need to detect +} + +DOS_HW_OPL::~DOS_HW_OPL() +{ + DOS_HW_OPL::writeReg(0x0BD, 0); + if(s_type == CHIPTYPE_OPL3) + { + DOS_HW_OPL::writeReg(0x104, 0); + DOS_HW_OPL::writeReg(0x105, 0); + } +} + +void DOS_HW_OPL::writeReg(uint16_t addr, uint8_t data) +{ + assert(m_id <= 1); + unsigned o = addr >> 8; + unsigned port = s_OPLBase[m_id] + o * 2; + +# ifdef __DJGPP__ + outportb(port, addr); + + for(unsigned c = 0; c < 6; ++c) + inportb(port); + + outportb(port + 1, data); + + for(unsigned c = 0; c < 35; ++c) + inportb(port); +# endif + +# ifdef __WATCOMC__ + outp(port, addr); + + for(uint16_t c = 0; c < 6; ++c) + inp(port); + + outp(port + 1, data); + + for(uint16_t c = 0; c < 35; ++c) + inp(port); +# endif//__WATCOMC__ +} + +void DOS_HW_OPL::nativeGenerate(int16_t *frame) +{ + frame[0] = 0; + frame[1] = 0; +} + +const char *DOS_HW_OPL::emulatorName() +{ + return s_devName; +} + +bool DOS_HW_OPL::hasFullPanning() +{ + return false; +} + +OPLChipBase::ChipType DOS_HW_OPL::chipType() +{ + return s_type; +} diff --git a/src/chips/dos_hw_opl.h b/src/chips/dos_hw_opl.h new file mode 100644 index 0000000..5e3750b --- /dev/null +++ b/src/chips/dos_hw_opl.h @@ -0,0 +1,48 @@ +/* + * Interfaces over Yamaha OPL3 (YMF262) chip emulators + * + * Copyright (c) 2017-2025 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 DOS_HW_OPL_H +#define DOS_HW_OPL_H + +#include "opl_chip_base.h" + +class DOS_HW_OPL : public OPLChipBaseT<DOS_HW_OPL> +{ +public: + DOS_HW_OPL(); + virtual ~DOS_HW_OPL() override; + + static void setChipType(ChipType type); + static void setOplAddress(uint16_t address); + + 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; + bool hasFullPanning() override; +}; + +#endif // DOS_HW_OPL_H diff --git a/utils/midiplay/adlmidiplay.cpp b/utils/midiplay/adlmidiplay.cpp index f5f30d8..fff74d5 100644 --- a/utils/midiplay/adlmidiplay.cpp +++ b/utils/midiplay/adlmidiplay.cpp @@ -29,12 +29,14 @@ #include <cstdlib> #include <cstring> #include <cstdarg> -#include <deque> #include <vector> #include <algorithm> -#include <signal.h> #include <stdint.h> -#include "utf8main.h" +#ifndef ADLMIDI_ENABLE_HW_DOS +# include <deque> +# include <signal.h> +# include "utf8main.h" +#endif #if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) # ifdef ADLMIDI_USE_SDL2 @@ -265,7 +267,7 @@ void mch_delay(int32_t msec) #include <adlmidi.h> -#ifndef HARDWARE_OPL3 +#ifndef ADLMIDI_ENABLE_HW_DOS #ifndef OUTPUT_WAVE_ONLY # include "audio.h" @@ -406,7 +408,7 @@ const char* audio_format_to_str(int format, int is_msb) return "UNK"; } -#endif //HARDWARE_OPL3 +#endif //ADLMIDI_ENABLE_HW_DOS const char* volume_model_to_str(int vm) { @@ -475,7 +477,7 @@ static void printError(const char *err, const char *what = NULL) } static int stop = 0; -#ifndef HARDWARE_OPL3 +#ifndef ADLMIDI_ENABLE_HW_DOS static void sighandler(int dum) { switch(dum) @@ -528,7 +530,7 @@ static void debugPrint(void * /*userdata*/, const char *fmt, ...) } } -#ifdef HARDWARE_OPL3 +#ifdef ADLMIDI_ENABLE_HW_DOS static inline void keyWait() { std::printf("\n<press any key to continue...>"); @@ -551,7 +553,7 @@ static void printBanks() for(int a = 0; a < banksCount; ++a) { std::printf("%10s%2u = %s\n", a ? "" : "Banks:", a, banknames[a]); -#ifdef HARDWARE_OPL3 +#ifdef ADLMIDI_ENABLE_HW_DOS if(((a - 15) % 23 == 0 && a != 0)) keyWait(); #endif @@ -624,7 +626,7 @@ static struct TimeCounter double realTimeStart; #endif -#ifdef HARDWARE_OPL3 +#ifdef ADLMIDI_ENABLE_HW_DOS unsigned newTimerFreq; unsigned timerPeriod; int haveYield; @@ -652,7 +654,7 @@ static struct TimeCounter printsCounter = 0; complete_prev = -1; -#ifndef HARDWARE_OPL3 +#ifndef ADLMIDI_ENABLE_HW_DOS printsCounterPeriod = 1; #else printsCounterPeriod = 20; @@ -666,7 +668,7 @@ static struct TimeCounter #endif } -#ifdef HARDWARE_OPL3 +#ifdef ADLMIDI_ENABLE_HW_DOS void initDosTimer() { # ifdef __DJGPP__ @@ -986,7 +988,7 @@ static void runHWSerialLoop(ADL_MIDIPlayer *myDevice) #endif // ADLMIDI_ENABLE_HW_SERIAL -#ifndef HARDWARE_OPL3 +#ifndef ADLMIDI_ENABLE_HW_DOS # ifndef OUTPUT_WAVE_ONLY static int runAudioLoop(ADL_MIDIPlayer *myDevice, AudioOutputSpec &spec) { @@ -1233,7 +1235,7 @@ static int runWaveOutLoopLoop(ADL_MIDIPlayer *myDevice, const std::string &musPa return 0; } -#else // HARDWARE_OPL3 +#else // ADLMIDI_ENABLE_HW_DOS static void runDOSLoop(ADL_MIDIPlayer *myDevice) { double tick_delay = 0.0; @@ -1272,374 +1274,592 @@ static void runDOSLoop(ADL_MIDIPlayer *myDevice) adl_panic(myDevice); //Shut up all sustaining notes } -#endif // HARDWARE_OPL3 +#endif // ADLMIDI_ENABLE_HW_DOS -int main(int argc, char **argv) + + +static struct Args { - std::fprintf(stdout, "==========================================\n" - #ifdef HARDWARE_OPL3 - " libADLMIDI demo utility (HW OPL)\n" - #else - " libADLMIDI demo utility\n" - #endif - "==========================================\n\n"); - flushout(stdout); + int setHwVibrato; + int setHwTremolo; + int setScaleMods; - if(argc >= 2 && std::string(argv[1]) == "--list-banks") - { - printBanks(); - return 0; - } + int setBankNo; + std::string setBankFile; + int setNum4op; + int setNumChips; - if(argc < 2 || std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h") - { - std::printf( - "Usage: adlmidi <midifilename> [ <options> ] \n" - " [ <bank> [ <numchips> [ <numfourops>] ] ]\n" - // " -p Enables adlib percussion instrument mode\n" - " -t Enables tremolo amplification mode\n" - " -v Enables vibrato amplification mode\n" - " -s Enables scaling of modulator volumes\n" - " -vm <num> Chooses one of volume models: \n" - " 0 auto (default)\n" - " 1 Generic\n" - " 2 Native OPL3\n" - " 3 DMX\n" - " 4 Apogee Sound System\n" - " 5 9x SB16\n" - " 6 DMX (Fixed AM voices)\n" - " 7 Apogee Sound System (Fixed AM voices)\n" - " 8 Audio Interface Library (AIL)\n" - " 9 9x Generic FM\n" - " 10 HMI Sound Operating System\n" - ); -#ifdef HARDWARE_OPL3 - keyWait(); -#endif - std::printf( - " -frb Enables full-ranged CC74 XG Brightness controller\n" - " -nl Quit without looping\n" - " -w Write WAV file rather than playing\n" - " -mb Run the test of multibank over embedded. 62, 14, 68, and 74'th banks\n" - " will be combined into one\n" - " --solo <track> Selects a solo track to play\n" - " --only <track1,...,trackN> Selects a subset of tracks to play\n" - " --song <song ID 0...N-1> Selects a song to play (if XMI)\n" - " -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" - " --emu-opal Uses Opal OPL3 emulator\n" - " --emu-java Uses Java OPL3 emulator\n" - " --emu-esfmu Uses ESFMu OPL3/ESFM emulator\n" -#endif -#ifdef HARDWARE_OPL3 - "\n" - " --time-freq <hz> Uses a different time value, DEFAULT 209\n" - " --list-banks Print a lost of all built-in FM banks\n" -#endif - "\n" - "Where <bank> - number of embeeded bank or filepath to custom WOPL bank file\n" - "\n" - "Note: To create WOPL bank files use OPL Bank Editor you can get here: \n" - "https://github.com/Wohlstand/OPL3BankEditor\n" - "\n" - ); + std::string musPath; + + int setFullRangeBright; -#ifndef HARDWARE_OPL3 - printBanks(); + int enableFullPanning; + +#ifndef OUTPUT_WAVE_ONLY + bool recordWave; + int loopEnabled; #endif - return 0; - } - unsigned int sampleRate = 44100; -#if !defined(HARDWARE_OPL3) && !defined(OUTPUT_WAVE_ONLY) + unsigned int sampleRate; + +#if !defined(ADLMIDI_ENABLE_HW_DOS) && !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; + const double AudioBufferLength; AudioOutputSpec spec; - spec.freq = sampleRate; - spec.format = ADLMIDI_SampleType_S16; - spec.is_msb = 0; - spec.channels = 2; - spec.samples = uint16_t((double)spec.freq * AudioBufferLength); -#endif // !HARDWARE_OPL3 && !OUTPUT_WAVE_ONLY - -#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!) - myDevice = adl_init(sampleRate); - if(myDevice == NULL) - { - printError("Failed to init MIDI device!\n"); - return 1; - } +#ifdef ADLMIDI_ENABLE_HW_DOS + ADL_UInt16 setHwAddress; + int setChipType; +#endif - //Set internal debug messages hook to print all libADLMIDI's internal debug messages - adl_setDebugMessageHook(myDevice, debugPrint, NULL); +#if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) + bool hwSerial; + std::string serialName; + unsigned serialBaud; + unsigned serialProto; +#endif /* * Set library options by parsing of command line arguments */ - bool multibankFromEnbededTest = false; + bool multibankFromEnbededTest; + + int autoArpeggioEnabled; + int chanAlloc; + +#ifndef ADLMIDI_ENABLE_HW_DOS + int emulator; +#endif + int volumeModel; + + size_t soloTrack; + int songNumLoad; + std::vector<size_t> onlyTracks; + + + Args() : + setHwVibrato(-1) + , setHwTremolo(-1) + , setScaleMods(-1) + , setBankNo(-1) + , setNum4op(-1) + , setNumChips(-1) + + , setFullRangeBright(-1) + , enableFullPanning(-1) #ifndef OUTPUT_WAVE_ONLY - bool recordWave = false; - int loopEnabled = 1; + , recordWave(false) + , loopEnabled(1) #endif - int autoArpeggioEnabled = 0; - int chanAlloc = ADLMIDI_ChanAlloc_AUTO; -#ifndef HARDWARE_OPL3 - int emulator = ADLMIDI_EMU_NUKED; + , sampleRate(44100) + +#if !defined(ADLMIDI_ENABLE_HW_DOS) && !defined(OUTPUT_WAVE_ONLY) + , AudioBufferLength(0.08) #endif - int volumeModel = ADLMIDI_VolumeModel_AUTO; - size_t soloTrack = ~(size_t)0; - int songNumLoad = -1; - std::vector<size_t> onlyTracks; +#ifdef ADLMIDI_ENABLE_HW_DOS + , setHwAddress(0) + , setChipType(ADLMIDI_DOS_ChipAuto) +#endif -#if !defined(HARDWARE_OPL3) && !defined(OUTPUT_WAVE_ONLY) - g_audioFormat.type = ADLMIDI_SampleType_S16; - g_audioFormat.containerSize = sizeof(int16_t); - g_audioFormat.sampleOffset = sizeof(int16_t) * 2; +#if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) + , hwSerial(false) + , serialBaud(115200) + , serialProto(ADLMIDI_SerialProtocol_RetroWaveOPL3) #endif + , multibankFromEnbededTest(false) + , autoArpeggioEnabled(0) + , chanAlloc(ADLMIDI_ChanAlloc_AUTO) - while(argc > 2) +#ifndef ADLMIDI_ENABLE_HW_DOS + , emulator(ADLMIDI_EMU_NUKED) +#endif + , volumeModel(ADLMIDI_VolumeModel_AUTO) + , soloTrack(~(size_t)0) + , songNumLoad(-1) { - bool had_option = false; +#if !defined(ADLMIDI_ENABLE_HW_DOS) && !defined(OUTPUT_WAVE_ONLY) + spec.freq = sampleRate; + spec.format = ADLMIDI_SampleType_S16; + spec.is_msb = 0; + spec.channels = 2; + spec.samples = uint16_t((double)spec.freq * AudioBufferLength); +#endif + } - if(!std::strcmp("-p", argv[2])) - fprintf(stderr, "Warning: -p argument is deprecated and useless!\n"); //adl_setPercMode(myDevice, 1);//Turn on AdLib percussion mode - else if(!std::strcmp("-v", argv[2])) - adl_setHVibrato(myDevice, 1);//Force turn on deep vibrato -#if !defined(OUTPUT_WAVE_ONLY) && !defined(HARDWARE_OPL3) - else if(!std::strcmp("-w", argv[2])) + int parseArgs(int argc, char **argv_arr, bool *quit) + { + const char* const* argv = argv_arr; + + if(argc >= 2 && std::string(argv[1]) == "--list-banks") { - //Current Wave output implementation allows only SINT16 output - g_audioFormat.type = ADLMIDI_SampleType_S16; - g_audioFormat.containerSize = sizeof(int16_t); - g_audioFormat.sampleOffset = sizeof(int16_t) * 2; - recordWave = true;//Record library output into WAV file + printBanks(); + *quit = true; + return 0; } - else if(!std::strcmp("-s8", argv[2]) && !recordWave) - spec.format = ADLMIDI_SampleType_S8; - else if(!std::strcmp("-u8", argv[2]) && !recordWave) - spec.format = ADLMIDI_SampleType_U8; - else if(!std::strcmp("-s16", argv[2]) && !recordWave) - spec.format = ADLMIDI_SampleType_S16; - else if(!std::strcmp("-u16", argv[2]) && !recordWave) - spec.format = ADLMIDI_SampleType_U16; - else if(!std::strcmp("-s32", argv[2]) && !recordWave) - spec.format = ADLMIDI_SampleType_S32; - else if(!std::strcmp("-f32", argv[2]) && !recordWave) - spec.format = ADLMIDI_SampleType_F32; -#endif - - else if(!std::strcmp("-t", argv[2])) - adl_setHTremolo(myDevice, 1);//Force turn on deep tremolo - - else if(!std::strcmp("-frb", argv[2])) - adl_setFullRangeBrightness(myDevice, 1);//Turn on a full-ranged XG CC74 Brightness -#ifndef OUTPUT_WAVE_ONLY - else if(!std::strcmp("-nl", argv[2])) - loopEnabled = 0; //Enable loop -#endif - else if(!std::strcmp("-na", argv[2])) // Deprecated - autoArpeggioEnabled = 0; //Enable auto-arpeggio - else if(!std::strcmp("-ea", argv[2])) - autoArpeggioEnabled = 1; //Enable auto-arpeggio - -#ifndef HARDWARE_OPL3 - else if(!std::strcmp("--emu-nuked", argv[2])) - emulator = ADLMIDI_EMU_NUKED; - else if(!std::strcmp("--emu-nuked7", argv[2])) - emulator = ADLMIDI_EMU_NUKED_174; - else if(!std::strcmp("--emu-dosbox", argv[2])) - emulator = ADLMIDI_EMU_DOSBOX; - else if(!std::strcmp("--emu-opal", argv[2])) - emulator = ADLMIDI_EMU_OPAL; - else if(!std::strcmp("--emu-java", argv[2])) - emulator = ADLMIDI_EMU_JAVA; - else if(!std::strcmp("--emu-esfmu", argv[2])) - emulator = ADLMIDI_EMU_ESFMu; - else if(!std::strcmp("--emu-mame-opl2", argv[2])) - emulator = ADLMIDI_EMU_MAME_OPL2; - else if(!std::strcmp("--emu-ymfm-opl2", argv[2])) - emulator = ADLMIDI_EMU_YMFM_OPL2; - else if(!std::strcmp("--emu-ymfm-opl3", argv[2])) - emulator = ADLMIDI_EMU_YMFM_OPL3; -#endif -#if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) - else if(!std::strcmp("--serial", argv[2])) + if(argc < 2 || std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h") { - if(argc <= 3) + const char *help_text = + "Usage: adlmidi <midifilename> [ <options> ] \n" + " [ <bank> [ <numchips> [ <numfourops>] ] ]\n" + "\n" + //------------------------------------------------------------------------------| + "Where <bank> - number of embeeded bank or filepath to custom WOPL bank file\n" + "Where <numchips> - total number of parallel emulated chips running to\n" + " extend poliphony (on hardware 1 chip can only be used)\n" + "Where <numfourops> - total number of 4-operator channels on OPL3 chips.\n" + " By defaullt value depends on the used bank.\n" + "\n" + // " -p Enables adlib percussion instrument mode\n" + " -t Enables force deep tremolo mode (Default: depens on bank)\n" + " -v Enables force deep vibrato mode (Default: depens on bank)\n" + " -s Enables scaling of modulator volumes\n" + " -vm <num> Chooses one of volume models: \n" + " 0 auto (default)\n" + " 1 Generic\n" + " 2 Native OPL3\n" + " 3 DMX\n" + " 4 Apogee Sound System\n" + " 5 9x SB16\n" + " 6 DMX (Fixed AM voices)\n" + " 7 Apogee Sound System (Fixed AM voices)\n" + " 8 Audio Interface Library (AIL)\n" + " 9 9x Generic FM\n" + " 10 HMI Sound Operating System\n" + " -frb Enables full-ranged CC74 XG Brightness controller\n" + " -nl Quit without looping\n" + " -w Write WAV file rather than playing\n" + //------------------------------------------------------------------------------| + " -mb Run the test of multibank over embedded. 62, 14, 68, and 74'th banks\n" + " will be combined into one\n" + " --solo <track> Selects a solo track to play\n" + " --only <track1,...,trackN> Selects a subset of tracks to play\n" + " --song <song ID 0...N-1> Selects a song to play (if XMI)\n" + " -ea Enable the auto-arpeggio\n" +#ifndef ADLMIDI_ENABLE_HW_DOS + " -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" + " --emu-opal Uses Opal OPL3 emulator\n" + " --emu-java Uses Java OPL3 emulator\n" + " --emu-esfmu Uses ESFMu OPL3/ESFM emulator\n" +#else + "\n" + //------------------------------------------------------------------------------| + " --time-freq <hz> Uses a different time value, DEFAULT 209\n" + " --type <opl2|opl3> Type of hardware chip ('opl2' or 'opl3'), default AUTO\n" + " --addr <hex> Hardware address of the chip, DEFAULT 0x388\n" + " --list-banks Print a lost of all built-in FM banks\n" +#endif + "\n" + //------------------------------------------------------------------------------| + "Note: To create WOPL bank files use OPL Bank Editor you can get here: \n" + "https://github.com/Wohlstand/OPL3BankEditor\n" +#ifdef ADLMIDI_ENABLE_HW_DOS + "\n\n" + //------------------------------------------------------------------------------| + "TIP: If you have the SoundBlaster Pro with Dual OPL2, you can use two cips\n" + "if you specify the base address of sound card itself (for example 0x220) and\n" + "set two chips. However, keep a note that SBPro's chips were designed for the\n" + "Stereo, not for polyphony, and therefore, you will hear voices randomly going\n" + "between left and right speaker.\n" +#endif + "\n" + ; + +#ifdef ADLMIDI_ENABLE_HW_DOS + int lines = 5; + const char *cur = help_text; + + for(; *cur != '\0'; ++cur) { - printError("The option --serial requires an argument!\n"); - return 1; + char c = *cur; + std::putc(c, stdout); + if(c == '\n') + lines++; + + if(lines >= 23) + { + keyWait(); + lines = 0; + } } - had_option = true; - hwSerial = true; - serialName = argv[3]; +#else + std::printf("%s", help_text); + flushout(stdout); +#endif + +#ifndef ADLMIDI_ENABLE_HW_DOS + printBanks(); +#endif + *quit = true; + return 0; } - else if(!std::strcmp("--serial-baud", argv[2])) + + musPath = argv[1]; + + while(argc > 2) { - if(argc <= 3) + bool had_option = false; + + if(!std::strcmp("-p", argv[2])) + fprintf(stderr, "Warning: -p argument is deprecated and useless!\n"); //adl_setPercMode(myDevice, 1);//Turn on AdLib percussion mode + else if(!std::strcmp("-v", argv[2])) + setHwVibrato = 1; + +#if !defined(OUTPUT_WAVE_ONLY) && !defined(ADLMIDI_ENABLE_HW_DOS) + else if(!std::strcmp("-w", argv[2])) { - printError("The option --serial-baud requires an argument!\n"); - return 1; + //Current Wave output implementation allows only SINT16 output + g_audioFormat.type = ADLMIDI_SampleType_S16; + g_audioFormat.containerSize = sizeof(int16_t); + g_audioFormat.sampleOffset = sizeof(int16_t) * 2; + recordWave = true;//Record library output into WAV file } - had_option = true; - serialBaud = std::strtol(argv[3], NULL, 10); - } - else if(!std::strcmp("--serial-proto", argv[2])) - { - if(argc <= 3) + else if(!std::strcmp("-s8", argv[2]) && !recordWave) + spec.format = ADLMIDI_SampleType_S8; + else if(!std::strcmp("-u8", argv[2]) && !recordWave) + spec.format = ADLMIDI_SampleType_U8; + else if(!std::strcmp("-s16", argv[2]) && !recordWave) + spec.format = ADLMIDI_SampleType_S16; + else if(!std::strcmp("-u16", argv[2]) && !recordWave) + spec.format = ADLMIDI_SampleType_U16; + else if(!std::strcmp("-s32", argv[2]) && !recordWave) + spec.format = ADLMIDI_SampleType_S32; + else if(!std::strcmp("-f32", argv[2]) && !recordWave) + spec.format = ADLMIDI_SampleType_F32; +#endif + + else if(!std::strcmp("-t", argv[2])) + setHwTremolo = 1; + + else if(!std::strcmp("-frb", argv[2])) + setFullRangeBright = 1; + +#ifndef OUTPUT_WAVE_ONLY + else if(!std::strcmp("-nl", argv[2])) + loopEnabled = 0; //Enable loop +#endif + else if(!std::strcmp("-na", argv[2])) // Deprecated + autoArpeggioEnabled = 0; //Enable auto-arpeggio + else if(!std::strcmp("-ea", argv[2])) + autoArpeggioEnabled = 1; //Enable auto-arpeggio + +#ifndef ADLMIDI_ENABLE_HW_DOS + else if(!std::strcmp("--emu-nuked", argv[2])) + emulator = ADLMIDI_EMU_NUKED; + else if(!std::strcmp("--emu-nuked7", argv[2])) + emulator = ADLMIDI_EMU_NUKED_174; + else if(!std::strcmp("--emu-dosbox", argv[2])) + emulator = ADLMIDI_EMU_DOSBOX; + else if(!std::strcmp("--emu-opal", argv[2])) + emulator = ADLMIDI_EMU_OPAL; + else if(!std::strcmp("--emu-java", argv[2])) + emulator = ADLMIDI_EMU_JAVA; + else if(!std::strcmp("--emu-esfmu", argv[2])) + emulator = ADLMIDI_EMU_ESFMu; + else if(!std::strcmp("--emu-mame-opl2", argv[2])) + emulator = ADLMIDI_EMU_MAME_OPL2; + else if(!std::strcmp("--emu-ymfm-opl2", argv[2])) + emulator = ADLMIDI_EMU_YMFM_OPL2; + else if(!std::strcmp("--emu-ymfm-opl3", argv[2])) + emulator = ADLMIDI_EMU_YMFM_OPL3; +#endif + +#if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) + else if(!std::strcmp("--serial", argv[2])) { - printError("The option --serial-proto requires an argument!\n"); - return 1; + if(argc <= 3) + { + printError("The option --serial requires an argument!\n"); + *quit = true; + 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"); + *quit = true; + 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"); + *quit = true; + return 1; + } + had_option = true; + serialProto = std::strtol(argv[3], NULL, 10); } - 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 + else if(!std::strcmp("-fp", argv[2])) + enableFullPanning = 1; + else if(!std::strcmp("-mb", argv[2])) + multibankFromEnbededTest = true; + else if(!std::strcmp("-s", argv[2])) + setScaleMods = 1; #ifndef ADLMIDI_HW_OPL - else if(!std::strcmp("--gain", argv[2])) - { - if(argc <= 3) + else if(!std::strcmp("--gain", argv[2])) { - printError("The option --gain requires an argument!\n"); - return 1; + if(argc <= 3) + { + printError("The option --gain requires an argument!\n"); + *quit = true; + return 1; + } + had_option = true; + g_gaining = std::atof(argv[3]); } - had_option = true; - g_gaining = std::atof(argv[3]); - } -#endif // HARDWARE_OPL3 +#endif // ADLMIDI_ENABLE_HW_DOS -#ifdef HARDWARE_OPL3 - else if(!std::strcmp("--time-freq", argv[2])) - { - if(argc <= 3) +#ifdef ADLMIDI_ENABLE_HW_DOS + else if(!std::strcmp("--time-freq", argv[2])) { - printError("The option --time-freq requires an argument!\n"); - return 1; - } + if(argc <= 3) + { + printError("The option --time-freq requires an argument!\n"); + *quit = true; + return 1; + } + + unsigned timerFreq = std::strtoul(argv[3], NULL, 0); + if(timerFreq == 0) + { + printError("The option --time-freq requires a non-zero integer argument!\n"); + *quit = true; + return 1; + } + + s_timeCounter.setDosTimerHZ(timerFreq); - unsigned timerFreq = std::strtoul(argv[3], NULL, 0); - if(timerFreq == 0) + had_option = true; + } + else if(!std::strcmp("--type", argv[2])) { - printError("The option --time-freq requires a non-zero integer argument!\n"); - return 1; + if(argc <= 3) + { + printError("The option --type requires an argument!\n"); + *quit = true; + return 1; + } + + if(!std::strcmp(argv[3], "opl3") || !std::strcmp(argv[3], "OPL3")) + setChipType = ADLMIDI_DOS_ChipOPL3; + else if(!std::strcmp(argv[3], "opl2") || !std::strcmp(argv[3], "OPL2")) + setChipType = ADLMIDI_DOS_ChipOPL2; + else + { + printError("Given invalid option for --type: accepted 'opl2' or 'opl3'!\n"); + *quit = true; + return 1; + } + + had_option = true; } + else if(!std::strcmp("--addr", argv[2])) + { + if(argc <= 3) + { + printError("The option --addr requires an argument!\n"); + *quit = true; + return 1; + } - s_timeCounter.setDosTimerHZ(timerFreq); + setHwAddress = std::strtoul(argv[3], NULL, 0); + if(setHwAddress == 0) + { + printError("The option --time-freq requires a non-zero integer argument!\n"); + *quit = true; + return 1; + } - had_option = true; - } + had_option = true; + } #endif - else if(!std::strcmp("-vm", argv[2])) - { - if(argc <= 3) + else if(!std::strcmp("-vm", argv[2])) { - printError("The option -vm requires an argument!\n"); - return 1; + if(argc <= 3) + { + printError("The option -vm requires an argument!\n"); + *quit = true; + return 1; + } + volumeModel = std::strtol(argv[3], NULL, 10); + had_option = true; } - volumeModel = std::strtol(argv[3], NULL, 10); - had_option = true; - } - else if(!std::strcmp("-ca", argv[2])) - { - if(argc <= 3) + else if(!std::strcmp("-ca", argv[2])) { - printError("The option -carequires an argument!\n"); - return 1; + if(argc <= 3) + { + printError("The option -carequires an argument!\n"); + *quit = true; + return 1; + } + + chanAlloc = std::strtol(argv[3], NULL, 10); + had_option = true; } - chanAlloc = std::strtol(argv[3], NULL, 10); - had_option = true; - } - else if(!std::strcmp("--solo", argv[2])) - { - if(argc <= 3) + else if(!std::strcmp("--solo", argv[2])) { - printError("The option --solo requires an argument!\n"); - return 1; + if(argc <= 3) + { + printError("The option --solo requires an argument!\n"); + *quit = true; + return 1; + } + soloTrack = std::strtoul(argv[3], NULL, 10); + had_option = true; } - soloTrack = std::strtoul(argv[3], NULL, 10); - had_option = true; - } - else if(!std::strcmp("--song", argv[2])) - { - if(argc <= 3) + else if(!std::strcmp("--song", argv[2])) { - printError("The option --song requires an argument!\n"); - return 1; + if(argc <= 3) + { + printError("The option --song requires an argument!\n"); + *quit = true; + return 1; + } + songNumLoad = std::strtol(argv[3], NULL, 10); + had_option = true; } - songNumLoad = std::strtol(argv[3], NULL, 10); - had_option = true; - } - else if(!std::strcmp("--only", argv[2])) - { - if(argc <= 3) + else if(!std::strcmp("--only", argv[2])) { - printError("The option --only requires an argument!\n"); - return 1; - } + if(argc <= 3) + { + printError("The option --only requires an argument!\n"); + *quit = true; + return 1; + } - const char *strp = argv[3]; - unsigned long value; - unsigned size; - bool err = std::sscanf(strp, "%lu%n", &value, &size) != 1; - while(!err && *(strp += size)) - { + const char *strp = argv[3]; + unsigned long value; + unsigned size; + bool err = std::sscanf(strp, "%lu%n", &value, &size) != 1; + while(!err && *(strp += size)) + { + onlyTracks.push_back(value); + err = std::sscanf(strp, ",%lu%n", &value, &size) != 1; + } + if(err) + { + printError("Invalid argument to --only!\n"); + *quit = true; + return 1; + } onlyTracks.push_back(value); - err = std::sscanf(strp, ",%lu%n", &value, &size) != 1; - } - if(err) - { - printError("Invalid argument to --only!\n"); - return 1; + had_option = true; } - onlyTracks.push_back(value); - had_option = true; + else + break; + + argv += (had_option ? 2 : 1); + argc -= (had_option ? 2 : 1); } - else break; + if(argc >= 3) + { + if(is_number(argv[2])) + setBankNo = std::atoi(argv[2]); + else + setBankFile = argv[2]; + } + + if(argc >= 4) + setNumChips = std::atoi(argv[3]); - std::copy(argv + (had_option ? 4 : 3), - argv + argc, - argv + 2); - argc -= (had_option ? 2 : 1); + if(argc >= 5) + setNum4op = std::atoi(argv[4]); + + *quit = false; + return 0; } +} s_devSetup; + + +int main(int argc, char **argv) +{ + std::fprintf(stdout, "==========================================\n" + #ifdef ADLMIDI_ENABLE_HW_DOS + " libADLMIDI demo utility (HW OPL)\n" + #else + " libADLMIDI demo utility\n" + #endif + "==========================================\n\n"); + flushout(stdout); + + bool doQuit = false; + int parseRet = s_devSetup.parseArgs(argc, argv, &doQuit); + + if(doQuit) + return parseRet; + + ADL_MIDIPlayer *myDevice; + +#ifdef ADLMIDI_ENABLE_HW_DOS + if(s_devSetup.setHwAddress > 0 || s_devSetup.setChipType != ADLMIDI_DOS_ChipAuto) + adl_switchDOSHW(s_devSetup.setChipType, s_devSetup.setHwAddress); +#endif + + //Initialize libADLMIDI and create the instance (you can initialize multiple of them!) + myDevice = adl_init(s_devSetup.sampleRate); + if(myDevice == NULL) + { + printError("Failed to init MIDI device!\n"); + return 1; + } + + //Set internal debug messages hook to print all libADLMIDI's internal debug messages + adl_setDebugMessageHook(myDevice, debugPrint, NULL); + +#if !defined(ADLMIDI_ENABLE_HW_DOS) && !defined(OUTPUT_WAVE_ONLY) + g_audioFormat.type = ADLMIDI_SampleType_S16; + g_audioFormat.containerSize = sizeof(int16_t); + g_audioFormat.sampleOffset = sizeof(int16_t) * 2; +#endif + + if(s_devSetup.setHwVibrato >= 0) + adl_setHVibrato(myDevice, s_devSetup.setHwVibrato);//Force turn on deep vibrato + + if(s_devSetup.setHwTremolo >= 0) + adl_setHTremolo(myDevice, s_devSetup.setHwTremolo);//Force turn on deep tremolo + + if(s_devSetup.setScaleMods >= 0) + adl_setScaleModulators(myDevice, s_devSetup.setScaleMods);//Turn on modulators scaling by volume + + if(s_devSetup.setFullRangeBright >= 0) + adl_setFullRangeBrightness(myDevice, s_devSetup.setFullRangeBright);//Turn on a full-ranged XG CC74 Brightness + + if(s_devSetup.enableFullPanning >= 0) + adl_setSoftPanEnabled(myDevice, s_devSetup.enableFullPanning); #ifndef OUTPUT_WAVE_ONLY //Turn loop on/off (for WAV recording loop must be disabled!) - adl_setLoopEnabled(myDevice, recordWave ? 0 : loopEnabled); + adl_setLoopEnabled(myDevice, s_devSetup.recordWave ? 0 : s_devSetup.loopEnabled); #endif - adl_setAutoArpeggio(myDevice, autoArpeggioEnabled); - adl_setChannelAllocMode(myDevice, chanAlloc); + adl_setAutoArpeggio(myDevice, s_devSetup.autoArpeggioEnabled); + adl_setChannelAllocMode(myDevice, s_devSetup.chanAlloc); #ifdef DEBUG_TRACE_ALL_EVENTS //Hook all MIDI events are ticking while generating an output buffer @@ -1648,60 +1868,59 @@ int main(int argc, char **argv) #endif #if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) - if(hwSerial) - adl_switchSerialHW(myDevice, serialName.c_str(), serialBaud, serialProto); + if(s_devSetup.hwSerial) + adl_switchSerialHW(myDevice, s_devSetup.serialName.c_str(), s_devSetup.serialBaud, s_devSetup.serialProto); else #endif -#ifndef HARDWARE_OPL3 - adl_switchEmulator(myDevice, emulator); +#ifndef ADLMIDI_ENABLE_HW_DOS + adl_switchEmulator(myDevice, s_devSetup.emulator); #endif std::fprintf(stdout, " - Library version %s\n", adl_linkedLibraryVersion()); -#ifdef HARDWARE_OPL3 - std::fprintf(stdout, " - Hardware OPL3 chip in use\n"); +#ifdef ADLMIDI_ENABLE_HW_DOS + std::fprintf(stdout, " - Hardware chip in use: %s\n", adl_chipEmulatorName(myDevice)); #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()); + if(s_devSetup.hwSerial) + std::fprintf(stdout, " - %s [device %s] in use\n", adl_chipEmulatorName(myDevice), s_devSetup.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(argc >= 3) + if(s_devSetup.setBankNo >= 0) { - if(is_number(argv[2])) + //Choose one of embedded banks + if(adl_setBank(myDevice, s_devSetup.setBankNo) != 0) { - int bankno = std::atoi(argv[2]); - //Choose one of embedded banks - if(adl_setBank(myDevice, bankno) != 0) - { - printError(adl_errorInfo(myDevice), "Can't set an embedded bank"); - adl_close(myDevice); - return 1; - } - std::fprintf(stdout, " - Use embedded bank #%d [%s]\n", bankno, adl_getBankNames()[bankno]); + printError(adl_errorInfo(myDevice), "Can't set an embedded bank"); + adl_close(myDevice); + return 1; } - else + + std::fprintf(stdout, " - Use embedded bank #%d [%s]\n", s_devSetup.setBankNo, adl_getBankNames()[s_devSetup.setBankNo]); + } + else if(!s_devSetup.setBankFile.empty()) + { + std::fprintf(stdout, " - Use custom bank [%s]...", s_devSetup.setBankFile.c_str()); + flushout(stdout); + + //Open external bank file (WOPL format is supported) + //to create or edit them, use OPL3 Bank Editor you can take here https://github.com/Wohlstand/OPL3BankEditor + if(adl_openBankFile(myDevice, s_devSetup.setBankFile.c_str()) != 0) { - std::string bankPath = argv[2]; - std::fprintf(stdout, " - Use custom bank [%s]...", bankPath.c_str()); + std::fprintf(stdout, "FAILED!\n"); flushout(stdout); - //Open external bank file (WOPL format is supported) - //to create or edit them, use OPL3 Bank Editor you can take here https://github.com/Wohlstand/OPL3BankEditor - if(adl_openBankFile(myDevice, bankPath.c_str()) != 0) - { - std::fprintf(stdout, "FAILED!\n"); - flushout(stdout); - printError(adl_errorInfo(myDevice), "Can't open a custom bank file"); - adl_close(myDevice); - return 1; - } - std::fprintf(stdout, "OK!\n"); + printError(adl_errorInfo(myDevice), "Can't open a custom bank file"); + adl_close(myDevice); + return 1; } + + std::fprintf(stdout, "OK!\n"); } - if(multibankFromEnbededTest) + + if(s_devSetup.multibankFromEnbededTest) { ADL_BankId id[] = { @@ -1736,13 +1955,14 @@ int main(int argc, char **argv) std::fprintf(stdout, " - Ran a test of multibank over embedded\n"); } -#ifndef HARDWARE_OPL3 +#ifndef ADLMIDI_ENABLE_HW_DOS int numOfChips = 4; - if(argc >= 4) - numOfChips = std::atoi(argv[3]); + + if(s_devSetup.setNumChips >= 0) + numOfChips = s_devSetup.setNumChips; #if defined(ADLMIDI_ENABLE_HW_SERIAL) && !defined(OUTPUT_WAVE_ONLY) - if(hwSerial) + if(s_devSetup.hwSerial) numOfChips = 1; #endif @@ -1753,18 +1973,19 @@ int main(int argc, char **argv) adl_close(myDevice); return 1; } + #else - int numOfChips = 1; - adl_setNumChips(myDevice, numOfChips); + if(s_devSetup.setNumChips >= 0 && s_devSetup.setNumChips <= 2 && s_devSetup.setChipType == ADLMIDI_DOS_ChipOPL2) + adl_setNumChips(myDevice, s_devSetup.setNumChips); #endif - if(volumeModel != ADLMIDI_VolumeModel_AUTO) - adl_setVolumeRangeModel(myDevice, volumeModel); + if(s_devSetup.volumeModel != ADLMIDI_VolumeModel_AUTO) + adl_setVolumeRangeModel(myDevice, s_devSetup.volumeModel); - if(argc >= 5) + if(s_devSetup.setNum4op >= 0) { //Set total count of 4-operator channels between all emulated chips - if(adl_setNumFourOpsChn(myDevice, std::atoi(argv[4])) != 0) + if(adl_setNumFourOpsChn(myDevice, s_devSetup.setNum4op) != 0) { printError(adl_errorInfo(myDevice), "Can't set number of 4-op channels"); adl_close(myDevice); @@ -1778,8 +1999,8 @@ int main(int argc, char **argv) adl_setLoopHooksOnly(myDevice, 1); #endif - if(songNumLoad >= 0) - adl_selectSongNum(myDevice, songNumLoad); + if(s_devSetup.songNumLoad >= 0) + adl_selectSongNum(myDevice, s_devSetup.songNumLoad); #if defined(DEBUG_SONG_SWITCHING) || defined(ENABLE_TERMINAL_HOTKEYS) set_conio_terminal_mode(); @@ -1789,10 +2010,11 @@ int main(int argc, char **argv) # endif #endif - std::string musPath = argv[1]; //Open MIDI file to play - if(adl_openFile(myDevice, musPath.c_str()) != 0) + if(adl_openFile(myDevice, s_devSetup.musPath.c_str()) != 0) { + std::fprintf(stdout, " - File [%s] failed!\n", s_devSetup.musPath.c_str()); + flushout(stdout); printError(adl_errorInfo(myDevice), "Can't open MIDI file"); adl_close(myDevice); return 2; @@ -1804,9 +2026,9 @@ 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 +#ifndef ADLMIDI_ENABLE_HW_DOS # ifdef ADLMIDI_ENABLE_HW_SERIAL - if(!hwSerial) + if(!s_devSetup.hwSerial) # endif { std::fprintf(stdout, " - Gain level: %g\n", g_gaining); @@ -1814,26 +2036,26 @@ int main(int argc, char **argv) #endif int songsCount = adl_getSongsCount(myDevice); - if(songNumLoad >= 0) - std::fprintf(stdout, " - Attempt to load song number: %d / %d\n", songNumLoad, songsCount); + if(s_devSetup.songNumLoad >= 0) + std::fprintf(stdout, " - Attempt to load song number: %d / %d\n", s_devSetup.songNumLoad, songsCount); else if(songsCount > 0) std::fprintf(stdout, " - File contains %d song(s)\n", songsCount); - if(soloTrack != ~static_cast<size_t>(0)) + if(s_devSetup.soloTrack != ~static_cast<size_t>(0)) { - std::fprintf(stdout, " - Solo track: %lu\n", static_cast<unsigned long>(soloTrack)); - adl_setTrackOptions(myDevice, soloTrack, ADLMIDI_TrackOption_Solo); + std::fprintf(stdout, " - Solo track: %lu\n", static_cast<unsigned long>(s_devSetup.soloTrack)); + adl_setTrackOptions(myDevice, s_devSetup.soloTrack, ADLMIDI_TrackOption_Solo); } - if(!onlyTracks.empty()) + if(!s_devSetup.onlyTracks.empty()) { size_t count = adl_trackCount(myDevice); for(size_t track = 0; track < count; ++track) adl_setTrackOptions(myDevice, track, ADLMIDI_TrackOption_Off); std::fprintf(stdout, " - Only tracks:"); - for(size_t i = 0, n = onlyTracks.size(); i < n; ++i) + for(size_t i = 0, n = s_devSetup.onlyTracks.size(); i < n; ++i) { - size_t track = onlyTracks[i]; + size_t track = s_devSetup.onlyTracks[i]; adl_setTrackOptions(myDevice, track, ADLMIDI_TrackOption_On); std::fprintf(stdout, " %lu", static_cast<unsigned long>(track)); } @@ -1842,46 +2064,46 @@ int main(int argc, char **argv) std::fprintf(stdout, " - Automatic arpeggio is turned %s\n", adl_getAutoArpeggio(myDevice) ? "ON" : "OFF"); - std::fprintf(stdout, " - File [%s] opened!\n", musPath.c_str()); + std::fprintf(stdout, " - File [%s] opened!\n", s_devSetup.musPath.c_str()); flushout(stdout); -#ifndef HARDWARE_OPL3 +#ifndef ADLMIDI_ENABLE_HW_DOS signal(SIGINT, sighandler); signal(SIGTERM, sighandler); # if !defined(_WIN32) && !defined(__WATCOMC__) signal(SIGHUP, sighandler); # endif -#else // HARDWARE_OPL3 +#else // ADLMIDI_ENABLE_HW_DOS //disable(); s_timeCounter.initDosTimer(); s_timeCounter.flushDosTimer(); //enable(); -#endif//HARDWARE_OPL3 +#endif//ADLMIDI_ENABLE_HW_DOS s_timeCounter.setTotal(adl_totalTimeLength(myDevice)); #ifndef OUTPUT_WAVE_ONLY s_timeCounter.setLoop(adl_loopStartTime(myDevice), adl_loopEndTime(myDevice)); -# ifndef HARDWARE_OPL3 - if(!recordWave) +# ifndef ADLMIDI_ENABLE_HW_DOS + if(!s_devSetup.recordWave) # endif { - std::fprintf(stdout, " - Loop is turned %s\n", loopEnabled ? "ON" : "OFF"); + std::fprintf(stdout, " - Loop is turned %s\n", s_devSetup.loopEnabled ? "ON" : "OFF"); 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 +# ifndef ADLMIDI_ENABLE_HW_DOS # ifdef ADLMIDI_ENABLE_HW_SERIAL - if(hwSerial) + if(s_devSetup.hwSerial) runHWSerialLoop(myDevice); else # endif { - int ret = runAudioLoop(myDevice, spec); + int ret = runAudioLoop(myDevice, s_devSetup.spec); if (ret != 0) { adl_close(myDevice); @@ -1896,22 +2118,22 @@ int main(int argc, char **argv) } #endif //OUTPUT_WAVE_ONLY -#ifndef HARDWARE_OPL3 +#ifndef ADLMIDI_ENABLE_HW_DOS # ifndef OUTPUT_WAVE_ONLY else # endif //OUTPUT_WAVE_ONLY { - int ret = runWaveOutLoopLoop(myDevice, musPath, sampleRate); + int ret = runWaveOutLoopLoop(myDevice, s_devSetup.musPath, s_devSetup.sampleRate); if(ret != 0) { adl_close(myDevice); return ret; } } -#endif //HARDWARE_OPL3 +#endif //ADLMIDI_ENABLE_HW_DOS -#ifdef HARDWARE_OPL3 +#ifdef ADLMIDI_ENABLE_HW_DOS s_timeCounter.restoreDosTimer(); #endif |