diff options
Diffstat (limited to 'utils/midiplay/adlmidiplay.cpp')
-rw-r--r-- | utils/midiplay/adlmidiplay.cpp | 1264 |
1 files changed, 904 insertions, 360 deletions
diff --git a/utils/midiplay/adlmidiplay.cpp b/utils/midiplay/adlmidiplay.cpp index fe0a758..6f2adea 100644 --- a/utils/midiplay/adlmidiplay.cpp +++ b/utils/midiplay/adlmidiplay.cpp @@ -34,6 +34,61 @@ #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 + +# define HAS_S_GETTIME +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 +264,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 +432,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 +569,663 @@ 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]; +#ifdef HAS_S_GETTIME + char realHMS[25]; +#endif + + bool hasLoop; + uint64_t milliseconds_prev; + int printsCounter; + int printsCounterPeriod; + int complete_prev; + double totalTime; + +#ifdef HAS_S_GETTIME + double realTimeStart; +#endif + +#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); +#ifdef HAS_S_GETTIME + realTimeStart = s_getTime(); + secondsToHMSM(s_getTime() - realTimeStart, realHMS, 25); +#endif + } + + 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); +#ifdef HAS_S_GETTIME + secondsToHMSM(s_getTime() - realTimeStart, realHMS, 25); +#endif + std::fprintf(stdout, " \r"); +#ifdef HAS_S_GETTIME + std::fprintf(stdout, "Time position: %s / %s [Real time: %s]\r", posHMS, totalHMS, realHMS); +#else + std::fprintf(stdout, "Time position: %s / %s\r", posHMS, totalHMS); +#endif + 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 +1280,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 +1307,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 +1428,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 +1488,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 +1589,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 +1601,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 +1618,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 +1635,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 +1662,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 +1683,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 +1708,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 +1719,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 +1733,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 +1744,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 +1793,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 +1842,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; } - |