#include #include #include #include #include #include #include #include class MutexType { SDL_mutex *mut; public: MutexType() : mut(SDL_CreateMutex()) { } ~MutexType() { SDL_DestroyMutex(mut); } void Lock() { SDL_mutexP(mut); } void Unlock() { SDL_mutexV(mut); } }; static std::deque AudioBuffer; static MutexType AudioBuffer_lock; static void SDL_AudioCallbackX(void *, Uint8 *stream, int len) { SDL_LockAudio(); short *target = (short *) stream; AudioBuffer_lock.Lock(); /*if(len != AudioBuffer.size()) fprintf(stderr, "len=%d stereo samples, AudioBuffer has %u stereo samples", len/4, (unsigned) AudioBuffer.size()/2);*/ unsigned ate = len / 2; // number of shorts if(ate > AudioBuffer.size()) ate = AudioBuffer.size(); for(unsigned a = 0; a < ate; ++a) { target[a] = AudioBuffer[a]; } AudioBuffer.erase(AudioBuffer.begin(), AudioBuffer.begin() + ate); AudioBuffer_lock.Unlock(); SDL_UnlockAudio(); } static bool is_number(const std::string &s) { std::string::const_iterator it = s.begin(); while(it != s.end() && std::isdigit(*it)) ++it; return !s.empty() && it == s.end(); } #undef main int main(int argc, char **argv) { if(argc < 2 || std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h") { std::printf( "Usage: adlmidi [ ] [ [ [ ] ] ]\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" " -nl Quit without looping\n" " -w Write WAV file rather than playing\n" "\n" "Where - 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" ); int banksCount = adl_getBanksCount(); const char* const* banknames = adl_getBankNames(); std::printf(" Available embedded banks by number:\n\n"); for(int a = 0; a < banksCount; ++a) std::printf("%10s%2u = %s\n", a ? "" : "Banks:", a, banknames[a]); std::printf( "\n" " Use banks 2-5 to play Descent \"q\" soundtracks.\n" " Look up the relevant bank number from descent.sng.\n" "\n" " The fourth parameter can be used to specify the number\n" " of four-op channels to use. Each four-op channel eats\n" " the room of two regular channels. Use as many as required.\n" " The Doom & Hexen sets require one or two, while\n" " Miles four-op set requires the maximum of numcards*6.\n" "\n" ); return 0; } //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. SDL_AudioSpec spec; SDL_AudioSpec obtained; spec.freq = 44100; spec.format = AUDIO_S16SYS; spec.channels = 2; spec.samples = spec.freq * AudioBufferLength; spec.callback = SDL_AudioCallbackX; // Set up SDL if(SDL_OpenAudio(&spec, &obtained) < 0) { std::fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError()); //return 1; } if(spec.samples != obtained.samples) std::fprintf(stderr, "Wanted (samples=%u,rate=%u,channels=%u); obtained (samples=%u,rate=%u,channels=%u)\n", spec.samples, spec.freq, spec.channels, obtained.samples, obtained.freq, obtained.channels); ADL_MIDIPlayer *myDevice; myDevice = adl_init(44100); if(myDevice == NULL) { std::fprintf(stderr, "Failed to init MIDI device!\n"); return 1; } while(argc > 2) { bool had_option = false; if(!std::strcmp("-p", argv[2])) adl_setPercMode(myDevice, 1); else if(!std::strcmp("-v", argv[2])) adl_setHVibrato(myDevice, 1); else if(!std::strcmp("-t", argv[2])) adl_setHTremolo(myDevice, 1); else if(!std::strcmp("-nl", argv[2])) adl_setLoopEnabled(myDevice, 0); else if(!std::strcmp("-s", argv[2])) adl_setScaleModulators(myDevice, 1); else break; std::copy(argv + (had_option ? 4 : 3), argv + argc, argv + 2); argc -= (had_option ? 2 : 1); } if(argc >= 3) { if(is_number(argv[2])) { int bankno = std::atoi(argv[2]); if(adl_setBank(myDevice, bankno) != 0) { std::fprintf(stderr, "%s\n", adl_errorString()); std::fflush(stderr); return 0; } } else { std::fprintf(stdout, "Loading custom bank file %s...", argv[2]); std::fflush(stdout); if(adl_openBankFile(myDevice, argv[2]) != 0) { std::fprintf(stdout, "FAILED!\n"); std::fflush(stdout); std::fprintf(stderr, "%s\n", adl_errorString()); std::fflush(stderr); return 0; } std::fprintf(stdout, "OK!\n"); std::fflush(stdout); } } if(argc >= 4) { if(adl_setNumCards(myDevice, std::atoi(argv[3])) != 0) { std::fprintf(stderr, "%s\n", adl_errorString()); return 0; } std::fprintf(stdout, "Number of cards %s\n", argv[3]); } if(argc >= 5) { if(adl_setNumFourOpsChn(myDevice, std::atoi(argv[4])) != 0) { std::fprintf(stderr, "%s\n", adl_errorString()); return 0; } std::fprintf(stdout, "Number of four-ops %s\n", argv[4]); } if(adl_openFile(myDevice, argv[1]) != 0) { std::fprintf(stderr, "%s\n", adl_errorString()); return 2; } SDL_PauseAudio(0); while(1) { short buff[4096]; unsigned long gotten = adl_play(myDevice, 4096, buff); if(gotten <= 0) break; AudioBuffer_lock.Lock(); size_t pos = AudioBuffer.size(); AudioBuffer.resize(pos + gotten); for(unsigned long p = 0; p < gotten; ++p) AudioBuffer[pos + p] = buff[p]; AudioBuffer_lock.Unlock(); const SDL_AudioSpec &spec_ = obtained; while(AudioBuffer.size() > spec_.samples + (spec_.freq * 2) * OurHeadRoomLength) { SDL_Delay(1); } } adl_close(myDevice); SDL_CloseAudio(); return 0; }