From 33ca6e6fadddf52aeed18a4e78b9983efca546bd Mon Sep 17 00:00:00 2001 From: sergm Date: Sun, 9 Feb 2014 20:03:58 +0200 Subject: [PATCH] - Added a patch for DOSBox SVN r3858 which uses the newly introduced ability to implement a MIDI device as a separate compilation unit. --- DOSBox-mt32-patch/README | 5 + .../dosbox-SVN-r3858-mt32-patch.diff | 414 ++++++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 DOSBox-mt32-patch/dosbox-SVN-r3858-mt32-patch.diff diff --git a/DOSBox-mt32-patch/README b/DOSBox-mt32-patch/README index 3e103b0..eeb14c3 100644 --- a/DOSBox-mt32-patch/README +++ b/DOSBox-mt32-patch/README @@ -13,3 +13,8 @@ syncThread/midi_mt32.h - version of the synth engine with synchronous rende asyncThread/midi_mt32.h - version of the synth engine with asynchronous rendering in a separate thread Compared to the sync version, consumes significantly less CPU time. Adds latency about 128 ms. + +=========================================================================================================== + +dosbox-SVN-r3858-mt32-patch.diff - diff file to be applied to DOSBox's source distribution. + It uses a bit different and clear approach introduced since SVN r3836. diff --git a/DOSBox-mt32-patch/dosbox-SVN-r3858-mt32-patch.diff b/DOSBox-mt32-patch/dosbox-SVN-r3858-mt32-patch.diff new file mode 100644 index 0000000..33bd20b --- /dev/null +++ b/DOSBox-mt32-patch/dosbox-SVN-r3858-mt32-patch.diff @@ -0,0 +1,414 @@ +diff --git a/src/Makefile.am b/src/Makefile.am +index 184469e..286638d 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -17,4 +17,4 @@ dosbox_LDADD = cpu/libcpu.a debug/libdebug.a dos/libdos.a fpu/libfpu.a hardware + + EXTRA_DIST = winres.rc dosbox.ico + +- ++LIBS += -lmt32emu +diff --git a/src/dosbox.cpp b/src/dosbox.cpp +index 6aad419..0ab8cba 100644 +--- a/src/dosbox.cpp ++++ b/src/dosbox.cpp +@@ -483,7 +483,7 @@ void DOSBOX_Init(void) { + + const char* mputypes[] = { "intelligent", "uart", "none",0}; + // FIXME: add some way to offer the actually available choices. +- const char *devices[] = { "default", "win32", "alsa", "oss", "coreaudio", "coremidi","none", 0}; ++ const char *devices[] = { "default", "win32", "alsa", "oss", "coreaudio", "coremidi", "mt32", "none", 0}; + Pstring = secprop->Add_string("mpu401",Property::Changeable::WhenIdle,"intelligent"); + Pstring->Set_values(mputypes); + Pstring->Set_help("Type of MPU-401 to emulate."); +@@ -499,6 +499,68 @@ void DOSBOX_Init(void) { + " In that case, add 'delaysysex', for example: midiconfig=2 delaysysex\n" + " See the README/Manual for more details."); + ++ Pstring = secprop->Add_string("mt32.romdir",Property::Changeable::WhenIdle,""); ++ Pstring->Set_help("Name of the directory where MT-32 Control and PCM ROM files can be found. Emulation requires these files to work.\n" ++ " Accepted file names are as follows:\n" ++ " MT32_CONTROL.ROM or CM32L_CONTROL.ROM - control ROM file.\n" ++ " MT32_PCM.ROM or CM32L_PCM.ROM - PCM ROM file.\n"); ++ ++ const char *mt32ReverseStereo[] = {"off", "on",0}; ++ Pstring = secprop->Add_string("mt32.reverse.stereo",Property::Changeable::WhenIdle,"off"); ++ Pstring->Set_values(mt32ReverseStereo); ++ Pstring->Set_help("Reverse stereo channels for MT-32 output"); ++ ++ const char *mt32log[] = {"off", "on",0}; ++ Pstring = secprop->Add_string("mt32.verbose",Property::Changeable::WhenIdle,"off"); ++ Pstring->Set_values(mt32log); ++ Pstring->Set_help("MT-32 debug logging"); ++ ++ const char *mt32thread[] = {"off", "on",0}; ++ Pstring = secprop->Add_string("mt32.thread",Property::Changeable::WhenIdle,"off"); ++ Pstring->Set_values(mt32thread); ++ Pstring->Set_help("MT-32 rendering in separate thread"); ++ ++ const char *mt32DACModes[] = {"0", "1", "2", "3", "auto",0}; ++ Pstring = secprop->Add_string("mt32.dac",Property::Changeable::WhenIdle,"auto"); ++ Pstring->Set_values(mt32DACModes); ++ Pstring->Set_help("MT-32 DAC input emulation mode\n" ++ "Nice = 0 - default\n" ++ "Produces samples at double the volume, without tricks.\n" ++ "Higher quality than the real devices\n\n" ++ ++ "Pure = 1\n" ++ "Produces samples that exactly match the bits output from the emulated LA32.\n" ++ "Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)\n" ++ "Much less likely to overdrive than any other mode.\n" ++ "Half the volume of any of the other modes, meaning its volume relative to the reverb\n" ++ "output when mixed together directly will sound wrong. So, reverb level must be lowered.\n" ++ "Perfect for developers while debugging :)\n\n" ++ ++ "GENERATION1 = 2\n" ++ "Re-orders the LA32 output bits as in early generation MT-32s (according to Wikipedia).\n" ++ "Bit order at DAC (where each number represents the original LA32 output bit number, and XX means the bit is always low):\n" ++ "15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 XX\n\n" ++ ++ "GENERATION2 = 3\n" ++ "Re-orders the LA32 output bits as in later geneerations (personally confirmed on my CM-32L - KG).\n" ++ "Bit order at DAC (where each number represents the original LA32 output bit number):\n" ++ "15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 14\n"); ++ ++ const char *mt32reverbModes[] = {"0", "1", "2", "3", "auto",0}; ++ Pstring = secprop->Add_string("mt32.reverb.mode",Property::Changeable::WhenIdle,"auto"); ++ Pstring->Set_values(mt32reverbModes); ++ Pstring->Set_help("MT-32 reverb mode"); ++ ++ const char *mt32reverbTimes[] = {"0", "1", "2", "3", "4", "5", "6", "7",0}; ++ Pint = secprop->Add_int("mt32.reverb.time",Property::Changeable::WhenIdle,5); ++ Pint->Set_values(mt32reverbTimes); ++ Pint->Set_help("MT-32 reverb decaying time"); ++ ++ const char *mt32reverbLevels[] = {"0", "1", "2", "3", "4", "5", "6", "7",0}; ++ Pint = secprop->Add_int("mt32.reverb.level",Property::Changeable::WhenIdle,3); ++ Pint->Set_values(mt32reverbLevels); ++ Pint->Set_help("MT-32 reverb level"); ++ + #if C_DEBUG + secprop=control->AddSection_prop("debug",&DEBUG_Init); + #endif +diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am +index 3fed5e6..ca36a07 100644 +--- a/src/gui/Makefile.am ++++ b/src/gui/Makefile.am +@@ -7,5 +7,5 @@ libgui_a_SOURCES = sdlmain.cpp sdl_mapper.cpp dosbox_logo.h \ + render_templates_sai.h render_templates_hq.h \ + render_templates_hq2x.h render_templates_hq3x.h \ + midi.cpp midi_win32.h midi_oss.h midi_coreaudio.h midi_alsa.h \ +- midi_coremidi.h sdl_gui.cpp dosbox_splash.h ++ midi_coremidi.h midi_mt32.h midi_mt32.cpp sdl_gui.cpp dosbox_splash.h + +diff --git a/src/gui/midi.cpp b/src/gui/midi.cpp +index ce4647f..dae3b7d 100644 +--- a/src/gui/midi.cpp ++++ b/src/gui/midi.cpp +@@ -91,6 +91,9 @@ MidiHandler Midi_none; + + #endif + ++#include "midi_mt32.h" ++static MidiHandler_mt32 &midiHandler_mt32 = MidiHandler_mt32::GetInstance(); ++ + DB_Midi midi; + + void MIDI_RawOutByte(Bit8u data) { +diff --git a/src/gui/midi_mt32.cpp b/src/gui/midi_mt32.cpp +new file mode 100644 +index 0000000..2f5d77b +--- /dev/null ++++ b/src/gui/midi_mt32.cpp +@@ -0,0 +1,228 @@ ++#include "midi_mt32.h" ++ ++#include ++#include ++#include ++#include "control.h" ++ ++MidiHandler_mt32 &MidiHandler_mt32::GetInstance() { ++ static MidiHandler_mt32 midiHandler_mt32; ++ return midiHandler_mt32; ++} ++ ++MidiHandler_mt32::MidiHandler_mt32() : open(false), chan(NULL), synth(NULL), thread(NULL) { ++} ++ ++MidiHandler_mt32::~MidiHandler_mt32() { ++ Close(); ++} ++ ++const char *MidiHandler_mt32::GetName(void) { ++ return "mt32"; ++} ++ ++void MidiHandler_mt32::makeROMPathName(char pathName[], const char romDir[], const char fileName[], bool addPathSeparator) { ++ strcpy(pathName, romDir); ++ if (addPathSeparator) { ++ strcat(pathName, "/"); ++ } ++ strcat(pathName, fileName); ++} ++ ++bool MidiHandler_mt32::Open(const char *conf) { ++ Section_prop *section = static_cast(control->GetSection("midi")); ++ const char *romDir = section->Get_string("mt32.romdir"); ++ if (romDir == NULL) romDir = "./"; // Paranoid NULL-check, should never happen ++ size_t romDirLen = strlen(romDir); ++ bool addPathSeparator = false; ++ if (romDirLen < 1) { ++ romDir = "./"; ++ } else if (4080 < romDirLen) { ++ LOG_MSG("MT32: mt32.romdir is too long, using the current dir."); ++ romDir = "./"; ++ } else { ++ char lastChar = romDir[strlen(romDir) - 1]; ++ addPathSeparator = lastChar != '/' && lastChar != '\\'; ++ } ++ ++ char pathName[4096]; ++ MT32Emu::FileStream controlROMFile; ++ MT32Emu::FileStream pcmROMFile; ++ ++ makeROMPathName(pathName, romDir, "CM32L_CONTROL.ROM", addPathSeparator); ++ if (!controlROMFile.open(pathName)) { ++ makeROMPathName(pathName, romDir, "MT32_CONTROL.ROM", addPathSeparator); ++ if (!controlROMFile.open(pathName)) { ++ LOG_MSG("MT32: Control ROM file not found"); ++ return false; ++ } ++ } ++ makeROMPathName(pathName, romDir, "CM32L_PCM.ROM", addPathSeparator); ++ if (!pcmROMFile.open(pathName)) { ++ makeROMPathName(pathName, romDir, "MT32_PCM.ROM", addPathSeparator); ++ if (!pcmROMFile.open(pathName)) { ++ LOG_MSG("MT32: PCM ROM file not found"); ++ return false; ++ } ++ } ++ const MT32Emu::ROMImage *controlROMImage = MT32Emu::ROMImage::makeROMImage(&controlROMFile); ++ const MT32Emu::ROMImage *pcmROMImage = MT32Emu::ROMImage::makeROMImage(&pcmROMFile); ++ synth = new MT32Emu::Synth(&reportHandler); ++ if (!synth->open(*controlROMImage, *pcmROMImage)) { ++ LOG_MSG("MT32: Error initialising emulation"); ++ return false; ++ } ++ MT32Emu::ROMImage::freeROMImage(controlROMImage); ++ MT32Emu::ROMImage::freeROMImage(pcmROMImage); ++ ++ if (strcmp(section->Get_string("mt32.reverb.mode"), "auto") != 0) { ++ Bit8u reverbsysex[] = {0x10, 0x00, 0x01, 0x00, 0x05, 0x03}; ++ reverbsysex[3] = (Bit8u)atoi(section->Get_string("mt32.reverb.mode")); ++ reverbsysex[4] = (Bit8u)section->Get_int("mt32.reverb.time"); ++ reverbsysex[5] = (Bit8u)section->Get_int("mt32.reverb.level"); ++ synth->writeSysex(16, reverbsysex, 6); ++ synth->setReverbOverridden(true); ++ } else { ++ LOG_MSG("MT32: Using default reverb"); ++ } ++ ++ if (strcmp(section->Get_string("mt32.dac"), "auto") != 0) { ++ synth->setDACInputMode((MT32Emu::DACInputMode)atoi(section->Get_string("mt32.dac"))); ++ } ++ ++ synth->setReversedStereoEnabled(strcmp(section->Get_string("mt32.reverse.stereo"), "on") == 0); ++ noise = strcmp(section->Get_string("mt32.verbose"), "on") == 0; ++ renderInThread = strcmp(section->Get_string("mt32.thread"), "on") == 0; ++ ++ playPos = 0; ++ chan = MIXER_AddChannel(mixerCallBack, MT32Emu::SAMPLE_RATE, "MT32"); ++ if (renderInThread) { ++ stopProcessing = false; ++ renderPos = 0; ++ render(FRAMES_PER_AUDIO_BUFFER - 1, audioBuffer); ++ playedBuffers = 1; ++ thread = SDL_CreateThread(processingThread, NULL); ++ } ++ chan->Enable(true); ++ ++ open = true; ++ return true; ++} ++ ++void MidiHandler_mt32::Close(void) { ++ if (!open) return; ++ chan->Enable(false); ++ if (renderInThread) { ++ stopProcessing = true; ++ SDL_WaitThread(thread, NULL); ++ thread = NULL; ++ } ++ MIXER_DelChannel(chan); ++ chan = NULL; ++ synth->close(); ++ delete synth; ++ synth = NULL; ++ open = false; ++} ++ ++void MidiHandler_mt32::PlayMsg(Bit8u *msg) { ++ if (renderInThread) { ++ synth->playMsg(SDL_SwapLE32(*(Bit32u *)msg), getMidiEventTimestamp()); ++ } else { ++ synth->playMsg(SDL_SwapLE32(*(Bit32u *)msg)); ++ } ++} ++ ++void MidiHandler_mt32::PlaySysex(Bit8u *sysex, Bitu len) { ++ if (renderInThread) { ++ synth->playSysex(sysex, len, getMidiEventTimestamp()); ++ } else { ++ synth->playSysex(sysex, len); ++ } ++} ++ ++void MidiHandler_mt32::render(const Bitu len, Bit16s *buf) { ++ Bit16s *revBuf = &buf[renderPos]; ++ synth->render(revBuf, len); ++ renderPos = (renderPos + (len << 1)) % AUDIO_BUFFER_SIZE; ++} ++ ++void MidiHandler_mt32::mixerCallBack(Bitu len) { ++ MidiHandler_mt32 &midiHandler_mt32 = MidiHandler_mt32::GetInstance(); ++ if (midiHandler_mt32.renderInThread) { ++ while (midiHandler_mt32.renderPos == midiHandler_mt32.playPos) { ++ SDL_Delay(1); ++ } ++ Bitu renderPos = midiHandler_mt32.renderPos; ++ Bitu playPos = midiHandler_mt32.playPos; ++ Bitu samplesReady; ++ if (renderPos < playPos) { ++ samplesReady = AUDIO_BUFFER_SIZE - playPos; ++ } else { ++ samplesReady = renderPos - playPos; ++ } ++ if (len > (samplesReady >> 1)) { ++ len = samplesReady >> 1; ++ } ++ midiHandler_mt32.chan->AddSamples_s16(len, &midiHandler_mt32.audioBuffer[playPos]); ++ playPos += (len << 1); ++ while (AUDIO_BUFFER_SIZE <= playPos) { ++ playPos -= AUDIO_BUFFER_SIZE; ++ midiHandler_mt32.playedBuffers++; ++ } ++ midiHandler_mt32.playPos = playPos; ++ } else { ++ midiHandler_mt32.renderPos = 0; ++ midiHandler_mt32.render(len, (Bit16s *)MixTemp); ++ midiHandler_mt32.chan->AddSamples_s16(len, (Bit16s *)MixTemp); ++ } ++} ++ ++int MidiHandler_mt32::processingThread(void *) { ++ MidiHandler_mt32 &midiHandler_mt32 = MidiHandler_mt32::GetInstance(); ++ while (!midiHandler_mt32.stopProcessing) { ++ Bitu renderPos = midiHandler_mt32.renderPos; ++ Bitu playPos = midiHandler_mt32.playPos; ++ Bitu framesToRender; ++ if (renderPos < playPos) { ++ framesToRender = (playPos - renderPos - 2) >> 1; ++ if (framesToRender < MINIMUM_RENDER_FRAMES) { ++ SDL_Delay(1 + (MILLIS_PER_SECOND * (MINIMUM_RENDER_FRAMES - framesToRender)) / SAMPLE_RATE); ++ continue; ++ } ++ } else { ++ framesToRender = (AUDIO_BUFFER_SIZE - renderPos) >> 1; ++ if (playPos == 0) { ++ framesToRender--; ++ if (framesToRender == 0) { ++ SDL_Delay(1 + (MILLIS_PER_SECOND * MINIMUM_RENDER_FRAMES) / SAMPLE_RATE); ++ continue; ++ } ++ } ++ } ++ midiHandler_mt32.render(framesToRender, midiHandler_mt32.audioBuffer); ++ } ++ return 0; ++} ++ ++void MidiHandler_mt32::MT32ReportHandler::onErrorControlROM() { ++ LOG_MSG("MT32: Couldn't open Control ROM file"); ++} ++ ++void MidiHandler_mt32::MT32ReportHandler::onErrorPCMROM() { ++ LOG_MSG("MT32: Couldn't open PCM ROM file"); ++} ++ ++void MidiHandler_mt32::MT32ReportHandler::showLCDMessage(const char *message) { ++ LOG_MSG("MT32: LCD-Message: %s", message); ++} ++ ++void MidiHandler_mt32::MT32ReportHandler::printDebug(const char *fmt, va_list list) { ++ MidiHandler_mt32 &midiHandler_mt32 = MidiHandler_mt32::GetInstance(); ++ if (midiHandler_mt32.noise) { ++ char s[1024]; ++ strcpy(s, "MT32: "); ++ vsnprintf(s + 6, 1017, fmt, list); ++ LOG_MSG(s); ++ } ++} +diff --git a/src/gui/midi_mt32.h b/src/gui/midi_mt32.h +new file mode 100644 +index 0000000..e401666 +--- /dev/null ++++ b/src/gui/midi_mt32.h +@@ -0,0 +1,57 @@ ++#ifndef DOSBOX_MIDI_MT32_H ++#define DOSBOX_MIDI_MT32_H ++ ++#include ++#include "mixer.h" ++#include "midi.h" ++ ++struct SDL_Thread; ++ ++class MidiHandler_mt32 : public MidiHandler { ++public: ++ static MidiHandler_mt32 &GetInstance(void); ++ ++ const char *GetName(void); ++ bool Open(const char *conf); ++ void Close(void); ++ void PlayMsg(Bit8u *msg); ++ void PlaySysex(Bit8u *sysex, Bitu len); ++ ++private: ++ static const Bitu SAMPLE_RATE = MT32Emu::SAMPLE_RATE; ++ static const Bitu MILLIS_PER_SECOND = 1000; ++ static const Bitu MINIMUM_RENDER_FRAMES = (16 * SAMPLE_RATE) / MILLIS_PER_SECOND; ++ static const Bitu AUDIO_BUFFER_SIZE = MIXER_BUFSIZE >> 1; ++ static const Bitu FRAMES_PER_AUDIO_BUFFER = AUDIO_BUFFER_SIZE >> 1; ++ ++ MixerChannel *chan; ++ MT32Emu::Synth *synth; ++ SDL_Thread *thread; ++ Bit16s audioBuffer[AUDIO_BUFFER_SIZE]; ++ volatile Bitu renderPos, playPos, playedBuffers; ++ volatile bool stopProcessing; ++ bool open, noise, renderInThread; ++ ++ class MT32ReportHandler : public MT32Emu::ReportHandler { ++ protected: ++ virtual void onErrorControlROM(); ++ virtual void onErrorPCMROM(); ++ virtual void showLCDMessage(const char *message); ++ virtual void printDebug(const char *fmt, va_list list); ++ } reportHandler; ++ ++ static void mixerCallBack(Bitu len); ++ static int processingThread(void *); ++ static void makeROMPathName(char pathName[], const char romDir[], const char fileName[], bool addPathSeparator); ++ ++ MidiHandler_mt32(); ++ ~MidiHandler_mt32(); ++ ++ void render(const Bitu len, Bit16s *buf); ++ ++ Bit32u inline getMidiEventTimestamp() { ++ return playedBuffers * FRAMES_PER_AUDIO_BUFFER + (playPos >> 1); ++ } ++}; ++ ++#endif /* DOSBOX_MIDI_MT32_H */