DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator
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         int     _control_fd;
00087 
00088         /* buffer for partial data read from _control_fd - from timidity-io.c, see fdgets() */
00089         char    _controlbuffer[BUFSIZ];
00090         int     _controlbuffer_count;   /* beginning of read pointer */
00091         int     _controlbuffer_size;    /* end of read pointer */
00092 
00093         TCPsocket control_socket, data_socket;
00094 };
00095 
00096 bool MidiHandler_timidity::Open(const char *conf) {
00097         char    timidity_host[512], * p, res[BUFSIZ];
00098         int     timidity_port, data_port, status;
00099 
00100         /* count ourselves open */
00101         if (_isOpen) return false;
00102         _isOpen = true;
00103 
00104         if (conf && conf[0]) safe_strncpy(timidity_host, conf, 512);
00105         else safe_strncpy(timidity_host, DEFAULT_TIMIDITY_HOST, 512);
00106 
00107         if ((p = strrchr(timidity_host, ',')) != NULL) {
00108                 *p++ = '\0';
00109                 _device_num = atoi(p);
00110         } else {
00111                 _device_num = 0;
00112         }
00113 
00114         /* extract control port */
00115         if ((p = strrchr(timidity_host, ':')) != NULL) {
00116                 *p++ = '\0';
00117                 timidity_port = atoi(p);
00118         } else {
00119                 timidity_port = DEFAULT_TIMIDITY_PORT;
00120         }
00121 
00122         /*
00123          * create control connection to the server
00124          */
00125         if ((connect_to_server(timidity_host, timidity_port, &control_socket)) == false) {
00126                 LOG_MSG("TiMidity: can't open control connection (host=%s, port=%d)", timidity_host, timidity_port);
00127                 return false;
00128         }
00129 
00130         /* should read greeting issued by server upon connect:
00131          * "220 TiMidity++ v2.13.2 ready)" */
00132         status = timidity_ctl_command(res, NULL);
00133         if (status != 220) {
00134                 LOG_MSG("TiMidity: bad response from server (host=%s, port=%d): %s", timidity_host, timidity_port, res);
00135                 close_all();
00136                 return false;
00137         }
00138 
00139         /*
00140          * setup buf and prepare data connection
00141          */
00142         /* should read: "200 OK" */
00143         status = timidity_ctl_command(res, "SETBUF %f %f", BUF_LOW_SYNC, BUF_HIGH_SYNC);
00144         if (status != 200)
00145                 LOG_MSG("TiMidity: bad reply for SETBUF command: %s", res);
00146 
00147         /* should read something like "200 63017 is ready acceptable",
00148          * where 63017 is port for data connection */
00149 #ifdef WORDS_BIGENDIAN
00150                 status = timidity_ctl_command(res, "OPEN msb");
00151 #else
00152                 status = timidity_ctl_command(res, "OPEN lsb");
00153 #endif
00154 
00155         if (status != 200) {
00156                 LOG_MSG("TiMidity: bad reply for OPEN command: %s", res);
00157                 close_all();
00158                 return false;
00159         }
00160 
00161         /*
00162          * open data connection
00163          */
00164         data_port = atoi(res + 4);
00165         if (connect_to_server(timidity_host, data_port, &data_socket) == false) {
00166                 LOG_MSG("TiMidity: can't open data connection (host=%s, port=%d)", timidity_host, data_port);
00167                 close_all();
00168                 return false;
00169         }
00170 
00171         /* should read message issued after connecting to data port:
00172          * "200 Ready data connection" */
00173         status = timidity_ctl_command(res, NULL);
00174         if (status != 200) {
00175                 LOG_MSG("Can't connect timidity: %s\t(host=%s, port=%d)\n", res, timidity_host, data_port);
00176                 close_all();
00177                 return false;
00178         }
00179 
00180         return true;
00181 }
00182 
00183 void MidiHandler_timidity::Close() {
00184         teardown();
00185 }
00186 
00187 void MidiHandler_timidity::close_all() {
00188         SDLNet_TCP_Close (control_socket);
00189         SDLNet_TCP_Close (data_socket);
00190 
00191         _isOpen = false;
00192 }
00193 
00194 void MidiHandler_timidity::teardown() {
00195         char res[BUFSIZ];
00196         int status;
00197 
00198         /* teardown connection to server (see timidity-io.c) if it
00199          * is initialized */
00200         if (_isOpen) {
00201                 timidity_eot();
00202                 timidity_sync(0);
00203 
00204                 /* scroll through all "302 Data connection is (already) closed"
00205                  * messages till we reach something like "200 Bye" */
00206                 do {
00207                         status = timidity_ctl_command(res, "QUIT");
00208                 } while (status && status != 302);
00209         }
00210 
00211         /* now close and nullify both filedescs */
00212         close_all();
00213 }
00214 
00215 bool MidiHandler_timidity::connect_to_server(const char* hostname, int tcp_port,
00216                                              TCPsocket * sd) {
00217         IPaddress ip;
00218 
00219         if (SDLNet_ResolveHost(&ip, hostname, tcp_port) < 0)
00220         {
00221                 LOG_MSG("SDLNet_ResolveHost: %s\n", SDLNet_GetError());
00222                 return false;
00223         }
00224 
00225         if (!(*sd = SDLNet_TCP_Open(&ip)))
00226         {
00227                 LOG_MSG("SDLNet_TCP_Open: %s\n", SDLNet_GetError());
00228                 return false;
00229         }
00230 
00231         return true;
00232 }
00233 
00234 int MidiHandler_timidity::timidity_ctl_command(char * buff, const char *fmt, ...) {
00235         int status, len;
00236         va_list ap;
00237 
00238         if (fmt != NULL) {
00239                 /* if argumends are present, write them to control connection */
00240                 va_start(ap, fmt);
00241                 len = vsnprintf(buff, BUFSIZ-1, fmt, ap); /* leave one byte for \n */
00242                 va_end(ap);
00243                 if (len <= 0 && len >= BUFSIZ-1) {
00244                         LOG_MSG("timidity_ctl_command: vsnprintf returned %d!\n", len);
00245                         return 0;
00246                 }
00247 
00248                 /* add newline if needed */
00249                 if (buff[len-1] != '\n')
00250                         buff[len++] = '\n';
00251 
00252                 /* write command to control socket */
00253                 if (SDLNet_TCP_Send(control_socket, buff, len) < len) {
00254                         LOG_MSG("SDLNet_TCP_Send: %s\n", SDLNet_GetError());
00255                 }
00256         }
00257 
00258         while (1) {
00259                 /* read reply */
00260                 if (fdgets(buff, BUFSIZ) <= 0) {
00261                         strcpy(buff, "Read error\n");
00262                         return 0;
00263                 }
00264 
00265                 /* report errors from server */
00266                 status = atoi(buff);
00267                 if (400 <= status && status <= 499) { /* Error of data stream */
00268                         LOG_MSG("TiMidity: error from server: %s", buff);
00269                         continue;
00270                 }
00271                 break;
00272         }
00273 
00274         return status;
00275 }
00276 
00277 void MidiHandler_timidity::timidity_meta_seq(int p1, int p2, int p3) {
00278         /* see _CHN_COMMON from soundcard.h; this is simplified
00279          * to just send seq to the server without any buffers,
00280          * delays and extra functions/macros */
00281         Bit8u seqbuf[8];
00282 
00283         seqbuf[0] = 0x92;
00284         seqbuf[1] = 0;
00285         seqbuf[2] = 0xff;
00286         seqbuf[3] = 0x7f;
00287         seqbuf[4] = p1;
00288         seqbuf[5] = p2;
00289         *(Bit16s *)&seqbuf[6] = p3;
00290 
00291         timidity_write_data(seqbuf, sizeof(seqbuf));
00292 }
00293 
00294 int MidiHandler_timidity::timidity_sync(int centsec) {
00295         char res[BUFSIZ];
00296         int status;
00297         unsigned long sleep_usec;
00298 
00299         timidity_meta_seq(0x02, 0x00, centsec); /* Wait playout */
00300 
00301         /* Wait "301 Sync OK" */
00302         do {
00303                 status = timidity_ctl_command(res, NULL);
00304 
00305                 if (status != 301)
00306                         LOG_MSG("TiMidity: error: SYNC: %s", res);
00307 
00308         } while (status && status != 301);
00309 
00310         if (status != 301)
00311                 return -1; /* error */
00312 
00313         sleep_usec = (unsigned long)(atof(res + 4) * 1000000);
00314 
00315         if (sleep_usec > 0)
00316                 SDL_Delay (sleep_usec / 1000);
00317 
00318         return 0;
00319 }
00320 
00321 int MidiHandler_timidity::timidity_eot(void) {
00322         timidity_meta_seq(0x00, 0x00, 0); /* End of playing */
00323         return timidity_sync(0);
00324 }
00325 
00326 void MidiHandler_timidity::timidity_write_data(const void *buf, int nbytes) {
00327         if (SDLNet_TCP_Send(data_socket, buf, nbytes) < nbytes) {
00328                 LOG_MSG("TiMidity: DATA WRITE FAILED (%s), DISABLING MUSIC OUTPUT", SDLNet_GetError());
00329                 close_all();
00330         }
00331 }
00332 
00333 int MidiHandler_timidity::fdgets(char *buff, size_t buff_size) {
00334         int n, count, size;
00335         char *buff_endp = buff + buff_size - 1, *pbuff, *beg;
00336 
00337         count = _controlbuffer_count;
00338         size = _controlbuffer_size;
00339         pbuff = _controlbuffer;
00340         beg = buff;
00341         do {
00342                 if (count == size) {
00343 
00344                         if ((n = SDLNet_TCP_Recv(control_socket, pbuff, BUFSIZ)) <= 0) {
00345                                 *buff = '\0';
00346                                 if (n == 0) {
00347                                         _controlbuffer_count = _controlbuffer_size = 0;
00348                                         return buff - beg;
00349                                 }
00350                                 return -1; /* < 0 error */
00351                         }
00352                         count = _controlbuffer_count = 0;
00353                         size = _controlbuffer_size = n;
00354                 }
00355                 *buff++ = pbuff[count++];
00356         } while (*(buff - 1) != '\n' && buff != buff_endp);
00357 
00358         *buff = '\0';
00359         _controlbuffer_count = count;
00360 
00361         return buff - beg;
00362 }
00363 
00364 void MidiHandler_timidity::PlayMsg(Bit8u *msg) {
00365         Bit8u buf[256];
00366         int position = 0;
00367 
00368         switch (msg[0] & 0xF0) {
00369         case 0x80:
00370         case 0x90:
00371         case 0xA0:
00372         case 0xB0:
00373         case 0xE0:
00374                 buf[position++] = SEQ_MIDIPUTC;
00375                 buf[position++] = msg[0];
00376                 buf[position++] = _device_num;
00377                 buf[position++] = 0;
00378                 buf[position++] = SEQ_MIDIPUTC;
00379                 buf[position++] = msg[1] & 0x7F;
00380                 buf[position++] = _device_num;
00381                 buf[position++] = 0;
00382                 buf[position++] = SEQ_MIDIPUTC;
00383                 buf[position++] = msg[2] & 0x7F;
00384                 buf[position++] = _device_num;
00385                 buf[position++] = 0;
00386                 break;
00387         case 0xC0:
00388         case 0xD0:
00389                 buf[position++] = SEQ_MIDIPUTC;
00390                 buf[position++] = msg[0];
00391                 buf[position++] = _device_num;
00392                 buf[position++] = 0;
00393                 buf[position++] = SEQ_MIDIPUTC;
00394                 buf[position++] = msg[1] & 0x7F;
00395                 buf[position++] = _device_num;
00396                 buf[position++] = 0;
00397                 break;
00398         default:
00399                 LOG_MSG("MidiHandler_timidity::PlayMsg: unknown : %08lx", (long)msg);
00400                 break;
00401         }
00402 
00403         timidity_write_data(buf, position);
00404 }
00405 
00406 void MidiHandler_timidity::PlaySysex(Bit8u *msg, Bitu length) {
00407         Bit8u buf[SYSEX_SIZE*4+8];
00408         int position = 0;
00409         const Bit8u *chr = msg;
00410 
00411         buf[position++] = SEQ_MIDIPUTC;
00412         buf[position++] = 0xF0;
00413         buf[position++] = _device_num;
00414         buf[position++] = 0;
00415         for (; length; --length, ++chr) {
00416                 buf[position++] = SEQ_MIDIPUTC;
00417                 buf[position++] = *chr & 0x7F;
00418                 buf[position++] = _device_num;
00419                 buf[position++] = 0;
00420         }
00421         buf[position++] = SEQ_MIDIPUTC;
00422         buf[position++] = 0xF7;
00423         buf[position++] = _device_num;
00424         buf[position++] = 0;
00425 
00426         timidity_write_data(buf, position);
00427 }
00428 
00429 MidiHandler_timidity Midi_timidity;
00430 
00431 #endif /*C_SDL_NET*/
00432