diff options
Diffstat (limited to 'utils/midiplay')
-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 |
5 files changed, 1041 insertions, 360 deletions
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 |