DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/gui/midi_mt32.h
00001 #include "mt32emu.h"
00002 #include <SDL_thread.h>
00003 #include <SDL_timer.h>
00004 #include "mixer.h"
00005 #include "control.h"
00006 
00007 class RingBuffer {
00008 private:
00009         static const unsigned int bufferSize = 1024;
00010         volatile unsigned int startpos;
00011         volatile unsigned int endpos;
00012         Bit64u ringBuffer[bufferSize];
00013 
00014 public:
00015         RingBuffer() {
00016                 startpos = 0;
00017                 endpos = 0;
00018         }
00019 
00020         bool put(Bit32u data) {
00021                 unsigned int newEndpos = endpos;
00022                 newEndpos++;
00023                 if (newEndpos == bufferSize) newEndpos = 0;
00024                 if (startpos == newEndpos) return false;
00025                 ringBuffer[endpos] = data;
00026                 endpos = newEndpos;
00027                 return true;
00028         }
00029 
00030         Bit32u get() {
00031                 if (startpos == endpos) return 0;
00032                 Bit32u data = (Bit32u)ringBuffer[startpos]; /* <- FIXME: Um.... really? */
00033                 startpos++;
00034                 if (startpos == bufferSize) startpos = 0;
00035                 return data;
00036         }
00037         void reset() {
00038                 startpos = 0;
00039                 endpos = 0;
00040         }
00041 };
00042 
00043 static class MidiHandler_mt32 : public MidiHandler {
00044 private:
00045         static const Bitu MIXER_BUFFER_SIZE = MIXER_BUFSIZE >> 2;
00046         MixerChannel *chan;
00047         MT32Emu::Synth *synth;
00048         RingBuffer midiBuffer;
00049         SDL_Thread *thread;
00050         SDL_mutex *synthMutex;
00051         SDL_semaphore *procIdleSem, *mixerReqSem;
00052         Bit16s mixerBuffer[2 * MIXER_BUFFER_SIZE];
00053         volatile Bitu mixerBufferSize;
00054         volatile bool stopProcessing;
00055         bool open, noise, reverseStereo, renderInThread;
00056         Bit16s numPartials;
00057 
00058         class MT32ReportHandler : public MT32Emu::ReportHandler {
00059         protected:
00060                 virtual void onErrorControlROM() {
00061                         LOG(LOG_MISC,LOG_WARN)("MT32: Couldn't open Control ROM file");
00062                 }
00063 
00064                 virtual void onErrorPCMROM() {
00065                         LOG(LOG_MISC,LOG_WARN)("MT32: Couldn't open PCM ROM file");
00066                 }
00067 
00068                 virtual void showLCDMessage(const char *message) {
00069                         LOG(LOG_MISC,LOG_DEBUG)("MT32: LCD-Message: %s", message);
00070                 }
00071 
00072                 virtual void printDebug(const char *fmt, va_list list);
00073         } reportHandler;
00074 
00075         static void mixerCallBack(Bitu len);
00076         static int processingThread(void *);
00077 
00078 public:
00079         MidiHandler_mt32() : chan(NULL), synth(NULL), thread(NULL), synthMutex(NULL), procIdleSem(NULL), mixerReqSem(NULL), open(false) {}
00080 
00081         ~MidiHandler_mt32() {
00082                 Close();
00083         }
00084 
00085         const char *GetName(void) {
00086                 return "mt32";
00087         }
00088 
00089     void user_romhelp(void) {
00090         /* don't just grunt and say "ROM file not found", help the user get the ROMs in place so we can work! */
00091         /* we're now 25 years past the era of limited RAM and "error code -25" we have more than enough disk and RAM
00092          * to contain explanatory text where the user went wrong! */
00093         LOG_MSG("MT32 emulation cannot work without the PCM and CONTROL ROM files.");
00094         LOG_MSG("To eliminate this error message, either change mididevice= to something else, or");
00095         LOG_MSG("place the ROM files in what will be the \"current working directory\" for DOSBox-X");
00096         LOG_MSG("when it starts up and initializes MIDI emulation.");
00097         LOG_MSG("The ROM files are: CM32L_CONTROL.ROM, MT32_CONTROL.ROM, CM32L_PCM.ROM, MT32_PCM.ROM");
00098     }
00099 
00100         bool Open(const char *conf) {
00101         (void)conf;//UNUSED
00102 
00103                 MT32Emu::FileStream controlROMFile;
00104                 MT32Emu::FileStream pcmROMFile;
00105 
00106         /* TODO: Add an option through midiconfig for the user to tell us where to look for ROMs.
00107          *       That way they don't have to sit there in the root directory of the DOS game alongside
00108          *       the game files. Much like Fluidsynth MIDI support where you can use midiconfig to
00109          *       tell it where the soundfonts are. */
00110 
00111                 if (!controlROMFile.open("CM32L_CONTROL.ROM")) {
00112                         if (!controlROMFile.open("MT32_CONTROL.ROM")) {
00113                                 LOG(LOG_MISC,LOG_WARN)("MT32: Control ROM file not found");
00114                 user_romhelp();
00115                                 return false;
00116                         }
00117                 }
00118                 if (!pcmROMFile.open("CM32L_PCM.ROM")) {
00119                         if (!pcmROMFile.open("MT32_PCM.ROM")) {
00120                                 LOG(LOG_MISC,LOG_WARN)("MT32: PCM ROM file not found");
00121                 user_romhelp();
00122                                 return false;
00123                         }
00124                 }
00125 
00126                 Section_prop *section = static_cast<Section_prop *>(control->GetSection("midi"));
00127 
00128                 const MT32Emu::ROMImage *controlROMImage = MT32Emu::ROMImage::makeROMImage(&controlROMFile);
00129                 const MT32Emu::ROMImage *pcmROMImage = MT32Emu::ROMImage::makeROMImage(&pcmROMFile);
00130 
00131                 synth = new MT32Emu::Synth(&reportHandler);
00132 
00133                 /* NTS: Read and apply mt32.partials BEFORE opening the synth, because the
00134                         synth assumes an initial number of partials, and allocates PartialManager()
00135                         instances which in turn initialize THAT many partials. If we later
00136                         call setPartialLimit() with the (often lower) number we wanted, then
00137                         a memory leak will occur because the PartialManager() will only free
00138                         the new lower limit and leave the rest in memory. */
00139                 numPartials = section->Get_int("mt32.partials");
00140                 if(numPartials>MT32EMU_MAX_PARTIALS) numPartials=MT32EMU_MAX_PARTIALS;
00141                 synth->setPartialLimit((unsigned int)numPartials);
00142 
00143                 if (!synth->open(*controlROMImage, *pcmROMImage)) {
00144                         LOG(LOG_MISC,LOG_WARN)("MT32: Error initialising emulation");
00145                         return false;
00146                 }
00147 
00148                 if (strcmp(section->Get_string("mt32.reverb.mode"), "auto") != 0) {
00149                         Bit8u reverbsysex[] = {0x10, 0x00, 0x01, 0x00, 0x05, 0x03};
00150                         reverbsysex[3] = (Bit8u)atoi(section->Get_string("mt32.reverb.mode"));
00151                         reverbsysex[4] = (Bit8u)section->Get_int("mt32.reverb.time");
00152                         reverbsysex[5] = (Bit8u)section->Get_int("mt32.reverb.level");
00153                         synth->writeSysex(16, reverbsysex, 6);
00154                         synth->setReverbOverridden(true);
00155                 } else {
00156                         LOG(LOG_MISC,LOG_DEBUG)("MT32: Using default reverb");
00157                 }
00158 
00159                 if (strcmp(section->Get_string("mt32.dac"), "auto") != 0) {
00160                         synth->setDACInputMode((MT32Emu::DACInputMode)atoi(section->Get_string("mt32.dac")));
00161                         // PURE mode = 1/2 reverb output
00162                         if( atoi(section->Get_string("mt32.dac")) == 1 ) {
00163                                 synth->setReverbOutputGain( 0.68f / 2 );
00164                         }
00165                 }
00166 
00167                 reverseStereo = strcmp(section->Get_string("mt32.reverse.stereo"), "on") == 0;
00168                 noise = strcmp(section->Get_string("mt32.verbose"), "on") == 0;
00169                 renderInThread = strcmp(section->Get_string("mt32.thread"), "on") == 0;
00170 
00171                 chan = MIXER_AddChannel(mixerCallBack, MT32Emu::SAMPLE_RATE, "MT32");
00172                 if (renderInThread) {
00173                         mixerBufferSize = 0;
00174                         stopProcessing = false;
00175                         synthMutex = SDL_CreateMutex();
00176                         procIdleSem = SDL_CreateSemaphore(0);
00177                         mixerReqSem = SDL_CreateSemaphore(0);
00178 #if defined(C_SDL2)
00179                         thread = SDL_CreateThread(processingThread, "MT32", NULL);
00180 #else
00181                         thread = SDL_CreateThread(processingThread, NULL);
00182 #endif
00183                         //if (thread == NULL || synthMutex == NULL || sleepMutex == NULL) renderInThread = false;
00184                 }
00185                 chan->Enable(true);
00186 
00187                 open = true;
00188                 return true;
00189         }
00190 
00191         void Close(void) {
00192                 if (!open) return;
00193                 chan->Enable(false);
00194                 if (renderInThread) {
00195                         stopProcessing = true;
00196                         SDL_SemPost(mixerReqSem);
00197                         SDL_WaitThread(thread, NULL);
00198                         thread = NULL;
00199                         SDL_DestroyMutex(synthMutex);
00200                         synthMutex = NULL;
00201                         SDL_DestroySemaphore(procIdleSem);
00202                         procIdleSem = NULL;
00203                         SDL_DestroySemaphore(mixerReqSem);
00204                         mixerReqSem = NULL;
00205                 }
00206                 MIXER_DelChannel(chan);
00207                 chan = NULL;
00208                 synth->close();
00209                 delete synth;
00210                 synth = NULL;
00211                 open = false;
00212         }
00213 
00214         void PlayMsg(Bit8u *msg) {
00215                 //if (!midiBuffer.put(*(Bit32u *)msg | (Bit64u(playPos + AUDIO_BUFFER_SIZE) << 32))) LOG_MSG("MT32: Playback buffer full!");
00216                 if (!midiBuffer.put(*(Bit32u *)msg)) { } //LOG_MSG("MT32: Playback buffer full!");
00217         }
00218 
00219         void PlaySysex(Bit8u *sysex, Bitu len) {
00220                 if (renderInThread) SDL_LockMutex(synthMutex);
00221                 synth->playSysex(sysex, len);
00222                 if (renderInThread) SDL_UnlockMutex(synthMutex);
00223         }
00224 
00225         MT32Emu::Synth* GetSynth() { return synth; }
00226 
00227         void Reset() {
00228                 midiBuffer.reset();
00229 
00230                 if (renderInThread) SDL_LockMutex(synthMutex);
00231                 mixerBufferSize = 0;
00232                 if (renderInThread) SDL_UnlockMutex(synthMutex);
00233         }
00234 
00235 private:
00236         void render(Bitu len, Bit16s *buf) {
00237                 Bit32u msg = midiBuffer.get();
00238                 if (msg != 0) synth->playMsg(msg);
00239                 synth->render(buf, len);
00240                 if (reverseStereo) {
00241                         Bit16s *revBuf = buf;
00242                         for(Bitu i = 0; i < len; i++) {
00243                                 Bit16s left = revBuf[0];
00244                                 Bit16s right = revBuf[1];
00245                                 *revBuf++ = right;
00246                                 *revBuf++ = left;
00247                         }
00248                 }
00249                 chan->AddSamples_s16(len, buf);
00250         }
00251 } midiHandler_mt32;
00252 
00253 void MidiHandler_mt32::MT32ReportHandler::printDebug(const char *fmt, va_list list) {
00254         if (midiHandler_mt32.noise) {
00255                 char s[1024];
00256                 strcpy(s, "MT32: ");
00257                 vsnprintf(s + 6, 1017, fmt, list);
00258                 LOG_MSG("%s", s);
00259         }
00260 }
00261 
00262 void MidiHandler_mt32::mixerCallBack(Bitu len) {
00263         if (midiHandler_mt32.renderInThread) {
00264                 SDL_SemWait(midiHandler_mt32.procIdleSem);
00265                 midiHandler_mt32.mixerBufferSize += len;
00266                 SDL_SemPost(midiHandler_mt32.mixerReqSem);
00267         } else {
00268                 midiHandler_mt32.render(len, (Bit16s *)MixTemp);
00269         }
00270 }
00271 
00272 int MidiHandler_mt32::processingThread(void *) {
00273         while (!midiHandler_mt32.stopProcessing) {
00274                 SDL_SemPost(midiHandler_mt32.procIdleSem);
00275                 SDL_SemWait(midiHandler_mt32.mixerReqSem);
00276                 for (;;) {
00277                         Bitu samplesToRender = midiHandler_mt32.mixerBufferSize;
00278                         if (samplesToRender == 0) break;
00279                         SDL_LockMutex(midiHandler_mt32.synthMutex);
00280                         midiHandler_mt32.render(samplesToRender, midiHandler_mt32.mixerBuffer);
00281                         SDL_UnlockMutex(midiHandler_mt32.synthMutex);
00282                         midiHandler_mt32.mixerBufferSize -= samplesToRender;
00283                 }
00284         }
00285         return 0;
00286 }