DOSBox-X
|
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