DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/gui/midi_synth.h
00001 /*
00002  *  Copyright (C) 2002-2020  The DOSBox Team
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU Library General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License along
00015  *  with this program; if not, write to the Free Software Foundation, Inc.,
00016  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00017  */
00018 
00019 #include <fluidsynth.h>
00020 #include <math.h>
00021 #include <string.h>
00022 #include "control.h"
00023 
00024 /* Protect against multiple inclusions */
00025 #ifndef MIXER_BUFSIZE
00026 #include "mixer.h"
00027 #endif
00028 
00029 static MixerChannel *synthchan = NULL;
00030 static fluid_synth_t *synth_soft = NULL;
00031 static int synthsamplerate = 0;
00032 
00033 static void synth_log(int level,
00034 #if !defined (FLUIDSYNTH_VERSION_MAJOR) || FLUIDSYNTH_VERSION_MAJOR >= 2 // Let version 2.x be the default
00035                       const
00036 #endif
00037                       char *message,
00038                       void *data) {
00039     (void)data;//UNUSED
00040 
00041         switch (level) {
00042         case FLUID_PANIC:
00043         case FLUID_ERR:
00044                 LOG(LOG_ALL,LOG_ERROR)(message);
00045                 break;
00046 
00047         case FLUID_WARN:
00048                 LOG(LOG_ALL,LOG_WARN)(message);
00049                 break;
00050 
00051         default:
00052                 LOG(LOG_ALL,LOG_NORMAL)(message);
00053                 break;
00054         }
00055 }
00056 
00057 static void synth_CallBack(Bitu len) {
00058         if (synth_soft != NULL) {
00059                 fluid_synth_write_s16(synth_soft, len, MixTemp, 0, 2, MixTemp, 1, 2);
00060                 synthchan->AddSamples_s16(len,(Bit16s *)MixTemp);
00061         }
00062 }
00063 
00064 #if defined (WIN32) || defined (OS2)
00065 #       define PATH_SEP "\\"
00066 #else
00067 #       define PATH_SEP "/"
00068 #endif
00069 
00070 class MidiHandler_synth: public MidiHandler {
00071 private:
00072         fluid_settings_t *settings;
00073         int sfont_id;
00074         bool isOpen;
00075 
00076         void PlayEvent(Bit8u *msg, Bitu len) {
00077                 Bit8u event = msg[0], channel, p1, p2;
00078 
00079                 switch (event) {
00080                 case 0xf0:
00081                 case 0xf7:
00082                         LOG(LOG_MISC,LOG_DEBUG)("SYNTH: sysex 0x%02x len %lu", (int)event, (long unsigned)len);
00083                         fluid_synth_sysex(synth_soft, (char *)(msg + 1), len - 1, NULL, NULL, NULL, 0);
00084                         return;
00085                 case 0xf9:
00086                         LOG(LOG_MISC,LOG_DEBUG)("SYNTH: midi tick");
00087                         return;
00088                 case 0xff:
00089                         LOG(LOG_MISC,LOG_DEBUG)("SYNTH: system reset");
00090                         fluid_synth_system_reset(synth_soft);
00091                         return;
00092                 case 0xf1: case 0xf2: case 0xf3: case 0xf4:
00093                 case 0xf5: case 0xf6: case 0xf8: case 0xfa:
00094                 case 0xfb: case 0xfc: case 0xfd: case 0xfe:
00095                         LOG(LOG_MISC,LOG_WARN)("SYNTH: unhandled event 0x%02x", (int)event);
00096                         return;
00097                 }
00098 
00099                 channel = event & 0xf;
00100                 p1 = len > 1 ? msg[1] : 0;
00101                 p2 = len > 2 ? msg[2] : 0;
00102 
00103                 LOG(LOG_MISC,LOG_DEBUG)("SYNTH: event 0x%02x channel %d, 0x%02x 0x%02x",
00104                         (int)event, (int)channel, (int)p1, (int)p2);
00105 
00106                 switch (event & 0xf0) {
00107                 case 0x80:
00108                         fluid_synth_noteoff(synth_soft, channel, p1);
00109                         break;
00110                 case 0x90:
00111                         fluid_synth_noteon(synth_soft, channel, p1, p2);
00112                         break;
00113                 case 0xb0:
00114                         fluid_synth_cc(synth_soft, channel, p1, p2);
00115                         break;
00116                 case 0xc0:
00117                         fluid_synth_program_change(synth_soft, channel, p1);
00118                         break;
00119                 case 0xd0:
00120                         fluid_synth_channel_pressure(synth_soft, channel, p1);
00121                         break;
00122                 case 0xe0:
00123                         fluid_synth_pitch_bend(synth_soft, channel, (p2 << 7) | p1);
00124                         break;
00125                 }
00126         };
00127 
00128 public:
00129         MidiHandler_synth() : MidiHandler(),isOpen(false) {};
00130 
00131         const char * GetName(void) {
00132                 return "synth";
00133         };
00134 
00135         bool Open(const char *conf) {
00136                 if (isOpen) return false;
00137 
00138                 /* Sound font file required */
00139                 if (!conf || (conf[0] == '\0')) {
00140                         LOG(LOG_MISC,LOG_DEBUG)("SYNTH: Specify .SF2 sound font file with midiconfig=");
00141                         return false;
00142                 }
00143 
00144                 fluid_set_log_function(FLUID_PANIC, synth_log, NULL);
00145                 fluid_set_log_function(FLUID_ERR, synth_log, NULL);
00146                 fluid_set_log_function(FLUID_WARN, synth_log, NULL);
00147                 fluid_set_log_function(FLUID_INFO, synth_log, NULL);
00148                 fluid_set_log_function(FLUID_DBG, synth_log, NULL);
00149 
00150                 /* Create the settings. */
00151                 settings = new_fluid_settings();
00152                 if (settings == NULL) {
00153                         LOG(LOG_MISC,LOG_WARN)("SYNTH: Error allocating MIDI soft synth settings");
00154                         return false;
00155                 }
00156 
00157                 fluid_settings_setstr(settings, "audio.sample-format", "16bits");
00158                 fluid_settings_setnum(settings, "synth.sample-rate", (double)synthsamplerate);
00159                 //fluid_settings_setnum(settings, "synth.gain", 0.5);
00160 
00161                 /* Create the synthesizer. */
00162                 synth_soft = new_fluid_synth(settings);
00163                 if (synth_soft == NULL) {
00164                         LOG(LOG_MISC,LOG_WARN)("SYNTH: Error initialising MIDI soft synth");
00165                         delete_fluid_settings(settings);
00166                         return false;
00167                 }
00168 
00169                 /* Load a SoundFont */
00170                 sfont_id = fluid_synth_sfload(synth_soft, conf, 0);
00171                 if (sfont_id == -1) {
00172                         extern std::string capturedir;
00173                         std::string str = capturedir + std::string(PATH_SEP) + std::string(conf);
00174                         sfont_id = fluid_synth_sfload(synth_soft, str.c_str(), 0);
00175                 }
00176 
00177                 if (sfont_id == -1) {
00178                         LOG(LOG_MISC,LOG_WARN)("SYNTH: Failed to load MIDI sound font file \"%s\"",
00179                            conf);
00180                         delete_fluid_synth(synth_soft);
00181                         delete_fluid_settings(settings);
00182                         return false;
00183                 }
00184 
00185                 synthchan = MIXER_AddChannel(synth_CallBack, (unsigned int)synthsamplerate, "SYNTH");
00186                 synthchan->Enable(false);
00187                 isOpen = true;
00188                 return true;
00189         };
00190 
00191         void Close(void) {
00192                 if (!isOpen) return;
00193 
00194                 synthchan->Enable(false);
00195                 MIXER_DelChannel(synthchan);
00196                 delete_fluid_synth(synth_soft);
00197                 delete_fluid_settings(settings);
00198 
00199                 synthchan = NULL;
00200                 synth_soft = NULL;
00201                 settings = NULL;
00202                 isOpen = false;
00203         };
00204 
00205         void PlayMsg(Bit8u *msg) {
00206                 synthchan->Enable(true);
00207                 PlayEvent(msg, MIDI_evt_len[*msg]);
00208         };
00209 
00210         void PlaySysex(Bit8u *sysex, Bitu len) {
00211                 PlayEvent(sysex, len);
00212         };
00213 };
00214 
00215 MidiHandler_synth Midi_synth;
00216 
00217 class MidiHandler_fluidsynth : public MidiHandler {
00218 private:
00219         std::string soundfont;
00220         int soundfont_id;
00221         fluid_settings_t *settings;
00222         fluid_synth_t *synth;
00223         fluid_audio_driver_t* adriver;
00224 public:
00225         MidiHandler_fluidsynth() : MidiHandler() {};
00226         const char* GetName(void) { return "fluidsynth"; }
00227         void PlaySysex(Bit8u * sysex, Bitu len) {
00228                 fluid_synth_sysex(synth, (char*)sysex, len, NULL, NULL, NULL, 0);
00229         }
00230 
00231         void PlayMsg(Bit8u * msg) {
00232                 unsigned char chanID = msg[0] & 0x0F;
00233                 switch (msg[0] & 0xF0) {
00234                 case 0x80:
00235                         fluid_synth_noteoff(synth, chanID, msg[1]);
00236                         break;
00237                 case 0x90:
00238                         fluid_synth_noteon(synth, chanID, msg[1], msg[2]);
00239                         break;
00240                 case 0xB0:
00241                         fluid_synth_cc(synth, chanID, msg[1], msg[2]);
00242                         break;
00243                 case 0xC0:
00244                         fluid_synth_program_change(synth, chanID, msg[1]);
00245                         break;
00246                 case 0xD0:
00247                         fluid_synth_channel_pressure(synth, chanID, msg[1]);
00248                         break;
00249                 case 0xE0: {
00250                         long theBend = ((long)msg[1] + (long)(msg[2] << 7));
00251                         fluid_synth_pitch_bend(synth, chanID, theBend);
00252                 }
00253                                    break;
00254                 default:
00255                         LOG(LOG_MISC, LOG_WARN)("MIDI:fluidsynth: Unknown Command: %08lx", (long)msg[0]);
00256                         break;
00257                 }
00258         }
00259 
00260         void Close(void) {
00261                 if (soundfont_id >= 0) {
00262                         fluid_synth_sfunload(synth, soundfont_id, 0);
00263                 }
00264                 delete_fluid_audio_driver(adriver);
00265                 delete_fluid_synth(synth);
00266                 delete_fluid_settings(settings);
00267         }
00268 
00269         bool Open(const char * conf) {
00270                 (void)conf;
00271 
00272                 Section_prop *section = static_cast<Section_prop *>(control->GetSection("midi"));
00273                 const char *sf = section->Get_string("fluid.soundfont");
00274                 if (!*sf) { // Let's try to find a soundfont before bailing
00275 #if defined (WIN32)
00276                         // default for windows according to fluidsynth docs
00277                         if (FILE *file = fopen("C:\\soundfonts\\default.sf2", "r")) {
00278                                 fclose(file);
00279                                 sf = "C:\\soundfonts\\default.sf2";
00280                         } else {
00281                                 LOG_MSG("MIDI:fluidsynth: SoundFont not specified");
00282                                 return false;
00283                         }
00284 #else
00285                         // Default on "other" platforms according to fluidsynth docs
00286                         // This works on RH and Fedora, if a soundfont is installed
00287                         if (FILE *file = fopen("/usr/share/soundfonts/default.sf2", "r")) {
00288                                 fclose(file);
00289                                 sf = "/usr/share/soundfonts/default.sf2";
00290                         // Ubuntu and Debian don't have a default.sf2...
00291                         } else if (FILE *file = fopen("/usr/share/sounds/sf2/FluidR3_GM.sf2", "r")) {
00292                                 fclose(file);
00293                                 sf = "/usr/share/sounds/sf2/FluidR3_GM.sf2";
00294                         } else {
00295                                 LOG_MSG("MIDI:fluidsynth: SoundFont not specified, and no system SoundFont found");
00296                                 return false;
00297                         }
00298 #endif
00299                 }
00300                 soundfont.assign(sf);
00301                 settings = new_fluid_settings();
00302                 if (strcmp(section->Get_string("fluid.driver"), "default") != 0) {
00303                         fluid_settings_setstr(settings, "audio.driver", section->Get_string("fluid.driver"));
00304                 }
00305 #if defined (__linux__) // Let's use pulseaudio as default on Linux, and not the FluidSynth default of Jack
00306                 else
00307                     fluid_settings_setstr(settings, "audio.driver", "pulseaudio");
00308 #endif
00309                 fluid_settings_setnum(settings, "synth.sample-rate", atof(section->Get_string("fluid.samplerate")));
00310                 fluid_settings_setnum(settings, "synth.gain", atof(section->Get_string("fluid.gain")));
00311                 fluid_settings_setint(settings, "synth.polyphony", section->Get_int("fluid.polyphony"));
00312                 if (strcmp(section->Get_string("fluid.cores"), "default") != 0) {
00313                         fluid_settings_setnum(settings, "synth.cpu-cores", atof(section->Get_string("fluid.cores")));
00314                 }
00315                 
00316                 std::string period=section->Get_string("fluid.periods"), periodsize=section->Get_string("fluid.periodsize");
00317                 if (period=="default") {
00318 #if defined (WIN32)
00319                         period="8";
00320 #else
00321                         period="16";
00322 #endif
00323                 }
00324                 if (periodsize=="default") {
00325 #if defined (WIN32)
00326                         periodsize="512";
00327 #else
00328                         periodsize="64";
00329 #endif
00330                 }
00331 #if !defined (FLUIDSYNTH_VERSION_MAJOR) || FLUIDSYNTH_VERSION_MAJOR >= 2
00332                 fluid_settings_setint(settings, "audio.periods", atoi(period.c_str()));
00333                 fluid_settings_setint(settings, "audio.period-size", atoi(periodsize.c_str()));
00334                 fluid_settings_setint(settings, "synth.reverb.active", !strcmp(section->Get_string("fluid.reverb"), "yes")?1:0);
00335                 fluid_settings_setint(settings, "synth.chorus.active", !strcmp(section->Get_string("fluid.chorus"), "yes")?1:0);
00336 #else
00337                 fluid_settings_setnum(settings, "audio.periods", atof(period.c_str()));
00338                 fluid_settings_setnum(settings, "audio.period-size", atof(periodsize.c_str()));
00339                 fluid_settings_setstr(settings, "synth.reverb.active", section->Get_string("fluid.reverb"));
00340                 fluid_settings_setstr(settings, "synth.chorus.active", section->Get_string("fluid.chorus"));
00341 #endif
00342 
00343                 synth = new_fluid_synth(settings);
00344                 if (!synth) {
00345                         LOG_MSG("MIDI:fluidsynth: Can't open synthesiser");
00346                         delete_fluid_settings(settings);
00347                         return false;
00348                 }
00349 
00350                 adriver = new_fluid_audio_driver(settings, synth);
00351                 if (!adriver) {
00352                         LOG_MSG("MIDI:fluidsynth: Can't create audio driver");
00353                         delete_fluid_synth(synth);
00354                         delete_fluid_settings(settings);
00355                         return false;
00356                 }
00357 
00358                 fluid_synth_set_reverb(synth, atof(section->Get_string("fluid.reverb.roomsize")), atof(section->Get_string("fluid.reverb.damping")), atof(section->Get_string("fluid.reverb.width")), atof(section->Get_string("fluid.reverb.level")));
00359 
00360                 fluid_synth_set_chorus(synth, section->Get_int("fluid.chorus.number"), atof(section->Get_string("fluid.chorus.level")), atof(section->Get_string("fluid.chorus.speed")), atof(section->Get_string("fluid.chorus.depth")), section->Get_int("fluid.chorus.type"));
00361 
00362                 /* Optionally load a soundfont */
00363                 if (!soundfont.empty()) {
00364                         soundfont_id = fluid_synth_sfload(synth, soundfont.c_str(), 1);
00365                         if (soundfont_id == FLUID_FAILED) {
00366                                 /* Just consider this a warning (fluidsynth already prints) */
00367                                 soundfont.clear();
00368                                 soundfont_id = -1;
00369                         }
00370                         else {
00371                                 LOG_MSG("MIDI:fluidsynth: Loaded SoundFont: %s", soundfont.c_str());
00372                         }
00373                 }
00374                 else {
00375                         soundfont_id = -1;
00376                         LOG_MSG("MIDI:fluidsynth: No SoundFont loaded");
00377                 }
00378                 return true;
00379         }
00380 };
00381 
00382 MidiHandler_fluidsynth Midi_fluidsynth;