DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/gui/midi_timidity.h
00001 /*
00002  * Output to TiMidity++ MIDI server support
00003  *                            by Dmitry Marakasov <amdmi3@amdmi3.ru>
00004  * based on:
00005  * - Raw output support (seq.cpp) by Michael Pearce
00006  * - Pseudo /dev/sequencer of TiMidity (timidity-io.c)
00007  *                                 by Masanao Izumo <mo@goice.co.jp>
00008  * - sys/soundcard.h by Hannu Savolainen (got from my FreeBSD
00009  *   distribution, for which it was modified by Luigi Rizzo)
00010  *
00011  */
00012 /* NTS: Anyone notice that while midi.cpp includes this file, it doesn't
00013  *      actually use the Timidity MIDI handler? You could delete this header
00014  *      entirely and remove the #include and it would have no effect on
00015  *      DOSBox-X. --J.C. */
00016 
00017 #ifdef C_SDL_NET
00018 //#ifdef C_TIMIDITY
00019 
00020 #include "SDL.h"
00021 #include "SDL_net.h"
00022 
00023 #define SEQ_MIDIPUTC 5
00024 
00025 #define TIMIDITY_LOW_DELAY
00026 
00027 #ifdef TIMIDITY_LOW_DELAY
00028 #define BUF_LOW_SYNC    0.1
00029 #define BUF_HIGH_SYNC   0.15
00030 #else
00031 #define BUF_LOW_SYNC    0.4
00032 #define BUF_HIGH_SYNC   0.8
00033 #endif
00034 
00035 /* default host & port */
00036 #define DEFAULT_TIMIDITY_HOST "127.0.0.1"
00037 #define DEFAULT_TIMIDITY_PORT 7777
00038 
00039 class MidiHandler_timidity: public MidiHandler {
00040 public:
00041         MidiHandler_timidity() : MidiHandler(),_isOpen(false),_device_num(0),
00042                                  _controlbuffer_count(0), _controlbuffer_size(0) {
00043                 if(!SDLNetInited) {
00044                         if (SDLNet_Init() == -1) {
00045                                 LOG_MSG("SDLNet_Init failed: %s\n", SDLNet_GetError());
00046                                 return;
00047                         }
00048                         SDLNetInited = true;
00049                 }
00050         };
00051 
00052         const char * GetName(void) { return "timidity";};
00053         bool    Open(const char * conf);
00054         void    Close(void);
00055         void    PlayMsg(Bit8u * msg);
00056         void    PlaySysex(Bit8u *msg, Bitu length);
00057 
00058 private:
00059         /* creates a tcp connection to TiMidity server, returns filedesc (like open()) */
00060         bool    connect_to_server(const char* hostname, int tcp_port, TCPsocket * sd);
00061 
00062         /* send command to the server; printf-like; returns http status and reply string (by reference). buff is expected to have at least BUFSIZ size */
00063         int     timidity_ctl_command(char *buff, const char *fmt, ...);
00064 
00065         /* timidity data socket-related stuff */
00066         void    timidity_meta_seq(int p1, int p2, int p3);
00067         int     timidity_sync(int centsec);
00068         int     timidity_eot();
00069 
00070         /* write() analogue for any midi data */
00071         void    timidity_write_data(const void *buf, int nbytes);
00072 
00073         /* get single line of server reply on control connection */
00074         int     fdgets(char *buff, size_t buff_size);
00075 
00076         /* teardown connection to server */
00077         void    teardown();
00078 
00079         /* close (if needed) and nullify both control and data filedescs */
00080         void    close_all();
00081 
00082 private:
00083         bool    _isOpen;
00084         int     _device_num;
00085 
00086         /* buffer for partial data read from _control_fd - from timidity-io.c, see fdgets() */
00087         char    _controlbuffer[BUFSIZ];
00088         int     _controlbuffer_count;   /* beginning of read pointer */
00089         int     _controlbuffer_size;    /* end of read pointer */
00090 
00091         TCPsocket control_socket, data_socket;
00092 };
00093 
00094 bool MidiHandler_timidity::Open(const char *conf) {
00095         char    timidity_host[512], * p, res[BUFSIZ];
00096         int     timidity_port, data_port, status;
00097 
00098         /* count ourselves open */
00099         if (_isOpen) return false;
00100         _isOpen = true;
00101 
00102         if (conf && conf[0]) safe_strncpy(timidity_host, conf, 512);
00103         else safe_strncpy(timidity_host, DEFAULT_TIMIDITY_HOST, 512);
00104 
00105         if ((p = strrchr(timidity_host, ',')) != NULL) {
00106                 *p++ = '\0';
00107                 _device_num = atoi(p);
00108         } else {
00109                 _device_num = 0;
00110         }
00111 
00112         /* extract control port */
00113         if ((p = strrchr(timidity_host, ':')) != NULL) {
00114                 *p++ = '\0';
00115                 timidity_port = atoi(p);
00116         } else {
00117                 timidity_port = DEFAULT_TIMIDITY_PORT;
00118         }
00119 
00120         /*
00121          * create control connection to the server
00122          */
00123         if ((connect_to_server(timidity_host, timidity_port, &control_socket)) == false) {
00124                 LOG_MSG("TiMidity: can't open control connection (host=%s, port=%d)", timidity_host, timidity_port);
00125                 return false;
00126         }
00127 
00128         /* should read greeting issued by server upon connect:
00129          * "220 TiMidity++ v2.13.2 ready)" */
00130         status = timidity_ctl_command(res, NULL);
00131         if (status != 220) {
00132                 LOG_MSG("TiMidity: bad response from server (host=%s, port=%d): %s", timidity_host, timidity_port, res);
00133                 close_all();
00134                 return false;
00135         }
00136 
00137         /*
00138          * setup buf and prepare data connection
00139          */
00140         /* should read: "200 OK" */
00141         status = timidity_ctl_command(res, "SETBUF %f %f", BUF_LOW_SYNC, BUF_HIGH_SYNC);
00142         if (status != 200)
00143                 LOG_MSG("TiMidity: bad reply for SETBUF command: %s", res);
00144 
00145         /* should read something like "200 63017 is ready acceptable",
00146          * where 63017 is port for data connection */
00147 #ifdef WORDS_BIGENDIAN
00148                 status = timidity_ctl_command(res, "OPEN msb");
00149 #else
00150                 status = timidity_ctl_command(res, "OPEN lsb");
00151 #endif
00152 
00153         if (status != 200) {
00154                 LOG_MSG("TiMidity: bad reply for OPEN command: %s", res);
00155                 close_all();
00156                 return false;
00157         }
00158 
00159         /*
00160          * open data connection
00161          */
00162         data_port = atoi(res + 4);
00163         if (connect_to_server(timidity_host, data_port, &data_socket) == false) {
00164                 LOG_MSG("TiMidity: can't open data connection (host=%s, port=%d)", timidity_host, data_port);
00165                 close_all();
00166                 return false;
00167         }
00168 
00169         /* should read message issued after connecting to data port:
00170          * "200 Ready data connection" */
00171         status = timidity_ctl_command(res, NULL);
00172         if (status != 200) {
00173                 LOG_MSG("Can't connect timidity: %s\t(host=%s, port=%d)\n", res, timidity_host, data_port);
00174                 close_all();
00175                 return false;
00176         }
00177 
00178         return true;
00179 }
00180 
00181 void MidiHandler_timidity::Close() {
00182         teardown();
00183 }
00184 
00185 void MidiHandler_timidity::close_all() {
00186         SDLNet_TCP_Close (control_socket);
00187         SDLNet_TCP_Close (data_socket);
00188 
00189         _isOpen = false;
00190 }
00191 
00192 void MidiHandler_timidity::teardown() {
00193         char res[BUFSIZ];
00194         int status;
00195 
00196         /* teardown connection to server (see timidity-io.c) if it
00197          * is initialized */
00198         if (_isOpen) {
00199                 timidity_eot();
00200                 timidity_sync(0);
00201 
00202                 /* scroll through all "302 Data connection is (already) closed"
00203                  * messages till we reach something like "200 Bye" */
00204                 do {
00205                         status = timidity_ctl_command(res, "QUIT");
00206                 } while (status && status != 302);
00207         }
00208 
00209         /* now close and nullify both filedescs */
00210         close_all();
00211 }
00212 
00213 bool MidiHandler_timidity::connect_to_server(const char* hostname, int tcp_port,
00214                                              TCPsocket * sd) {
00215         IPaddress ip;
00216 
00217         if (SDLNet_ResolveHost(&ip, hostname, tcp_port) < 0)
00218         {
00219                 LOG_MSG("SDLNet_ResolveHost: %s\n", SDLNet_GetError());
00220                 return false;
00221         }
00222 
00223         if (!(*sd = SDLNet_TCP_Open(&ip)))
00224         {
00225                 LOG_MSG("SDLNet_TCP_Open: %s\n", SDLNet_GetError());
00226                 return false;
00227         }
00228 
00229         return true;
00230 }
00231 
00232 int MidiHandler_timidity::timidity_ctl_command(char * buff, const char *fmt, ...) {
00233         int status, len;
00234         va_list ap;
00235 
00236         if (fmt != NULL) {
00237                 /* if argumends are present, write them to control connection */
00238                 va_start(ap, fmt);
00239                 len = vsnprintf(buff, BUFSIZ-1, fmt, ap); /* leave one byte for \n */
00240                 va_end(ap);
00241                 if (len <= 0 || len >= BUFSIZ-1) {
00242                         LOG_MSG("timidity_ctl_command: vsnprintf returned %d!\n", len);
00243                         return 0;
00244                 }
00245 
00246                 /* add newline if needed */
00247                 if (buff[len-1] != '\n')
00248                         buff[len++] = '\n';
00249 
00250                 /* write command to control socket */
00251                 if (SDLNet_TCP_Send(control_socket, buff, len) < len) {
00252                         LOG_MSG("SDLNet_TCP_Send: %s\n", SDLNet_GetError());
00253                 }
00254         }
00255 
00256         while (1) {
00257                 /* read reply */
00258                 if (fdgets(buff, BUFSIZ) <= 0) {
00259                         strcpy(buff, "Read error\n");
00260                         return 0;
00261                 }
00262 
00263                 /* report errors from server */
00264                 status = atoi(buff);
00265                 if (400 <= status && status <= 499) { /* Error of data stream */
00266                         LOG_MSG("TiMidity: error from server: %s", buff);
00267                         continue;
00268                 }
00269                 break;
00270         }
00271 
00272         return status;
00273 }
00274 
00275 void MidiHandler_timidity::timidity_meta_seq(int p1, int p2, int p3) {
00276         /* see _CHN_COMMON from soundcard.h; this is simplified
00277          * to just send seq to the server without any buffers,
00278          * delays and extra functions/macros */
00279         Bit8u seqbuf[8];
00280 
00281         seqbuf[0] = 0x92;
00282         seqbuf[1] = 0;
00283         seqbuf[2] = 0xff;
00284         seqbuf[3] = 0x7f;
00285         seqbuf[4] = p1;
00286         seqbuf[5] = p2;
00287         *(Bit16s *)&seqbuf[6] = p3;
00288 
00289         timidity_write_data(seqbuf, sizeof(seqbuf));
00290 }
00291 
00292 int MidiHandler_timidity::timidity_sync(int centsec) {
00293         char res[BUFSIZ];
00294         int status;
00295         unsigned long sleep_usec;
00296 
00297         timidity_meta_seq(0x02, 0x00, centsec); /* Wait playout */
00298 
00299         /* Wait "301 Sync OK" */
00300         do {
00301                 status = timidity_ctl_command(res, NULL);
00302 
00303                 if (status != 301)
00304                         LOG_MSG("TiMidity: error: SYNC: %s", res);
00305 
00306         } while (status && status != 301);
00307 
00308         if (status != 301)
00309                 return -1; /* error */
00310 
00311         sleep_usec = (unsigned long)(atof(res + 4) * 1000000);
00312 
00313         if (sleep_usec > 0)
00314                 SDL_Delay (sleep_usec / 1000);
00315 
00316         return 0;
00317 }
00318 
00319 int MidiHandler_timidity::timidity_eot(void) {
00320         timidity_meta_seq(0x00, 0x00, 0); /* End of playing */
00321         return timidity_sync(0);
00322 }
00323 
00324 void MidiHandler_timidity::timidity_write_data(const void *buf, int nbytes) {
00325         if (SDLNet_TCP_Send(data_socket, buf, nbytes) < nbytes) {
00326                 LOG_MSG("TiMidity: DATA WRITE FAILED (%s), DISABLING MUSIC OUTPUT", SDLNet_GetError());
00327                 close_all();
00328         }
00329 }
00330 
00331 int MidiHandler_timidity::fdgets(char *buff, size_t buff_size) {
00332         int n, count, size;
00333         char *buff_endp = buff + buff_size - 1, *pbuff, *beg;
00334 
00335         count = _controlbuffer_count;
00336         size = _controlbuffer_size;
00337         pbuff = _controlbuffer;
00338         beg = buff;
00339         do {
00340                 if (count == size) {
00341 
00342                         if ((n = SDLNet_TCP_Recv(control_socket, pbuff, BUFSIZ)) <= 0) {
00343                                 *buff = '\0';
00344                                 if (n == 0) {
00345                                         _controlbuffer_count = _controlbuffer_size = 0;
00346                                         return buff - beg;
00347                                 }
00348                                 return -1; /* < 0 error */
00349                         }
00350                         count = _controlbuffer_count = 0;
00351                         size = _controlbuffer_size = n;
00352                 }
00353                 *buff++ = pbuff[count++];
00354         } while (*(buff - 1) != '\n' && buff != buff_endp);
00355 
00356         *buff = '\0';
00357         _controlbuffer_count = count;
00358 
00359         return buff - beg;
00360 }
00361 
00362 void MidiHandler_timidity::PlayMsg(Bit8u *msg) {
00363         Bit8u buf[256];
00364         int position = 0;
00365 
00366         switch (msg[0] & 0xF0) {
00367         case 0x80:
00368         case 0x90:
00369         case 0xA0:
00370         case 0xB0:
00371         case 0xE0:
00372                 buf[position++] = SEQ_MIDIPUTC;
00373                 buf[position++] = msg[0];
00374                 buf[position++] = _device_num;
00375                 buf[position++] = 0;
00376                 buf[position++] = SEQ_MIDIPUTC;
00377                 buf[position++] = msg[1] & 0x7F;
00378                 buf[position++] = _device_num;
00379                 buf[position++] = 0;
00380                 buf[position++] = SEQ_MIDIPUTC;
00381                 buf[position++] = msg[2] & 0x7F;
00382                 buf[position++] = _device_num;
00383                 buf[position++] = 0;
00384                 break;
00385         case 0xC0:
00386         case 0xD0:
00387                 buf[position++] = SEQ_MIDIPUTC;
00388                 buf[position++] = msg[0];
00389                 buf[position++] = _device_num;
00390                 buf[position++] = 0;
00391                 buf[position++] = SEQ_MIDIPUTC;
00392                 buf[position++] = msg[1] & 0x7F;
00393                 buf[position++] = _device_num;
00394                 buf[position++] = 0;
00395                 break;
00396         default:
00397                 LOG_MSG("MidiHandler_timidity::PlayMsg: unknown : %08lx", (long)msg);
00398                 break;
00399         }
00400 
00401         timidity_write_data(buf, position);
00402 }
00403 
00404 void MidiHandler_timidity::PlaySysex(Bit8u *msg, Bitu length) {
00405         Bit8u buf[SYSEX_SIZE*4+8];
00406         int position = 0;
00407         const Bit8u *chr = msg;
00408 
00409         buf[position++] = SEQ_MIDIPUTC;
00410         buf[position++] = 0xF0;
00411         buf[position++] = _device_num;
00412         buf[position++] = 0;
00413         for (; length; --length, ++chr) {
00414                 buf[position++] = SEQ_MIDIPUTC;
00415                 buf[position++] = *chr & 0x7F;
00416                 buf[position++] = _device_num;
00417                 buf[position++] = 0;
00418         }
00419         buf[position++] = SEQ_MIDIPUTC;
00420         buf[position++] = 0xF7;
00421         buf[position++] = _device_num;
00422         buf[position++] = 0;
00423 
00424         timidity_write_data(buf, position);
00425 }
00426 
00427 MidiHandler_timidity Midi_timidity;
00428 
00429 #endif /*C_SDL_NET*/
00430