ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

timidity.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 /*
00024  * Output to TiMidity++ MIDI server support
00025  *                            by Dmitry Marakasov <amdmi3@amdmi3.ru>
00026  * based on:
00027  * - Raw output support (seq.cpp) by Michael Pearce
00028  * - Pseudo /dev/sequencer of TiMidity (timidity-io.c)
00029  *                                 by Masanao Izumo <mo@goice.co.jp>
00030  * - sys/soundcard.h by Hannu Savolainen (got from my FreeBSD
00031  *   distribution, for which it was modified by Luigi Rizzo)
00032  *
00033  */
00034 
00035 // Disable symbol overrides so that we can use system headers.
00036 #define FORBIDDEN_SYMBOL_ALLOW_ALL
00037 
00038 #include "common/scummsys.h"
00039 
00040 #if defined(USE_TIMIDITY)
00041 
00042 #include "common/endian.h"
00043 #include "common/error.h"
00044 #include "common/str.h"
00045 #include "common/textconsole.h"
00046 #include "audio/musicplugin.h"
00047 #include "audio/mpu401.h"
00048 
00049 #include <unistd.h>
00050 #include <stdio.h>
00051 #include <string.h>
00052 #include <sys/types.h>
00053 #include <sys/socket.h>
00054 #include <sys/param.h>
00055 #include <netdb.h>  /* for getaddrinfo */
00056 #include <netinet/in.h>
00057 #include <arpa/inet.h>
00058 #include <stdarg.h>
00059 #include <stdlib.h>
00060 #include <errno.h>
00061 
00062 // BeOS BONE uses snooze (x/1000) in place of usleep(x)
00063 #ifdef __BEOS__
00064 #define usleep(v) snooze(v/1000)
00065 #endif
00066 
00067 #define SEQ_MIDIPUTC 5
00068 
00069 #define TIMIDITY_LOW_DELAY
00070 
00071 #ifdef TIMIDITY_LOW_DELAY
00072 #define BUF_LOW_SYNC    0.1
00073 #define BUF_HIGH_SYNC   0.15
00074 #else
00075 #define BUF_LOW_SYNC    0.4
00076 #define BUF_HIGH_SYNC   0.8
00077 #endif
00078 
00079 /* default host & port */
00080 #define DEFAULT_TIMIDITY_HOST "127.0.0.1"
00081 #define DEFAULT_TIMIDITY_PORT "7777"
00082 
00083 class MidiDriver_TIMIDITY : public MidiDriver_MPU401 {
00084 public:
00085     MidiDriver_TIMIDITY();
00086 
00087     int open();
00088     bool isOpen() const { return _isOpen; }
00089     void close();
00090     void send(uint32 b);
00091     void sysEx(const byte *msg, uint16 length);
00092 
00093 private:
00094     /* creates a tcp connection to TiMidity server, returns filedesc (like open()) */
00095     int connect_to_server(const char* hostname, const char* tcp_port);
00096 
00097     /* send command to the server; printf-like; returns reply string */
00098     char *timidity_ctl_command(const char *fmt, ...) GCC_PRINTF(2, 3);
00099 
00100     /* timidity data socket-related stuff */
00101     void timidity_meta_seq(int p1, int p2, int p3);
00102     int timidity_sync(int centsec);
00103     int timidity_eot();
00104 
00105     /* write() analogue for any midi data */
00106     void timidity_write_data(const void *buf, size_t nbytes);
00107 
00108     /* get single line of server reply on control connection */
00109     int fdgets(char *buff, size_t buff_size);
00110 
00111     /* teardown connection to server */
00112     void teardown();
00113 
00114     /* close (if needed) and nullify both control and data filedescs */
00115     void close_all();
00116 
00117 private:
00118     bool _isOpen;
00119     int _device_num;
00120 
00121     int _control_fd;
00122     int _data_fd;
00123 
00124     /* buffer for partial data read from _control_fd - from timidity-io.c, see fdgets() */
00125     char _controlbuffer[BUFSIZ];
00126     int _controlbuffer_count;   /* beginning of read pointer */
00127     int _controlbuffer_size;    /* end of read pointer */
00128 };
00129 
00130 MidiDriver_TIMIDITY::MidiDriver_TIMIDITY() {
00131     _isOpen = false;
00132     _device_num = 0;
00133 
00134     /* init fd's */
00135     _control_fd = _data_fd = -1;
00136 
00137     /* init buffer for control connection */
00138     _controlbuffer_count = _controlbuffer_size = 0;
00139 }
00140 
00141 int MidiDriver_TIMIDITY::open() {
00142     char *res;
00143     char timidity_host[NI_MAXHOST];
00144     char timidity_port[6], data_port[6];
00145     int num;
00146 
00147     /* count ourselves open */
00148     if (_isOpen)
00149         return MERR_ALREADY_OPEN;
00150     _isOpen = true;
00151 
00152     /* get server hostname; if not specified in env, use default */
00153     if ((res = getenv("TIMIDITY_HOST")) == NULL)
00154         Common::strlcpy(timidity_host, DEFAULT_TIMIDITY_HOST, sizeof(timidity_host));
00155     else
00156         Common::strlcpy(timidity_host, res, sizeof(timidity_host));
00157 
00158     /* extract control port */
00159     if ((res = strrchr(timidity_host, ':')) != NULL) {
00160         *res++ = '\0';
00161         Common::strlcpy(timidity_port, res, sizeof(timidity_port));
00162     } else {
00163         Common::strlcpy(timidity_port, DEFAULT_TIMIDITY_PORT, sizeof(timidity_port));
00164     }
00165 
00166     /*
00167      * create control connection to the server
00168      */
00169     if ((_control_fd = connect_to_server(timidity_host, timidity_port)) < 0) {
00170         warning("TiMidity: can't open control connection (host=%s, port=%s)", timidity_host, timidity_port);
00171         return -1;
00172     }
00173 
00174     /* should read greeting issued by server upon connect:
00175      * "220 TiMidity++ v2.13.2 ready)" */
00176     res = timidity_ctl_command(NULL);
00177     if (atoi(res) != 220) {
00178         warning("TiMidity: bad response from server (host=%s, port=%s): %s", timidity_host, timidity_port, res);
00179         close_all();
00180         return -1;
00181     }
00182 
00183     /*
00184      * setup buf and prepare data connection
00185      */
00186     /* should read: "200 OK" */
00187     res = timidity_ctl_command("SETBUF %f %f", BUF_LOW_SYNC, BUF_HIGH_SYNC);
00188     if (atoi(res) != 200)
00189         warning("TiMidity: bad reply for SETBUF command: %s", res);
00190 
00191     /* should read something like "200 63017 is ready acceptable",
00192      * where 63017 is port for data connection */
00193 #ifdef SCUMM_LITTLE_ENDIAN
00194     res = timidity_ctl_command("OPEN lsb");
00195 #else
00196     res = timidity_ctl_command("OPEN msb");
00197 #endif
00198 
00199     if (atoi(res) != 200) {
00200         warning("TiMidity: bad reply for OPEN command: %s", res);
00201         close_all();
00202         return -1;
00203     }
00204 
00205     /*
00206      * open data connection
00207      */
00208     num = atoi(res + 4);
00209     if (num > 65535) {
00210         warning("TiMidity: Invalid port %d given.\n", num);
00211         close_all();
00212         return -1;
00213     }
00214     snprintf(data_port, sizeof(data_port), "%d", num);
00215     if ((_data_fd = connect_to_server(timidity_host, data_port)) < 0) {
00216         warning("TiMidity: can't open data connection (host=%s, port=%s)", timidity_host, data_port);
00217         close_all();
00218         return -1;
00219     }
00220 
00221     /* should read message issued after connecting to data port:
00222      * "200 Ready data connection" */
00223     res = timidity_ctl_command(NULL);
00224     if (atoi(res) != 200) {
00225         warning("Can't connect timidity: %s\t(host=%s, port=%s)", res, timidity_host, data_port);
00226         close_all();
00227         return -1;
00228     }
00229 
00230     /*
00231      * From seq.cpp
00232      */
00233     if (getenv("RESIDUALVM_MIDIPORT"))
00234         _device_num = atoi(getenv("RESIDUALVM_MIDIPORT"));
00235 
00236     return 0;
00237 }
00238 
00239 void MidiDriver_TIMIDITY::close() {
00240     teardown();
00241 
00242     MidiDriver_MPU401::close();
00243     _isOpen = false;
00244 }
00245 
00246 void MidiDriver_TIMIDITY::close_all() {
00247     if (_control_fd >= 0)
00248         ::close(_control_fd);
00249 
00250     if (_data_fd >= 0)
00251         ::close(_data_fd);
00252 
00253     _control_fd = _data_fd = -1;
00254 }
00255 
00256 void MidiDriver_TIMIDITY::teardown() {
00257     char *res;
00258 
00259     /* teardown connection to server (see timidity-io.c) if it
00260      * is initialized */
00261     if (_data_fd >= 0 && _control_fd >= 0) {
00262         timidity_eot();
00263         timidity_sync(0);
00264 
00265         /* scroll through all "302 Data connection is (already) closed"
00266          * messages till we reach something like "200 Bye" */
00267         do {
00268             res = timidity_ctl_command("QUIT");
00269         } while (*res && atoi(res) && atoi(res) != 302);
00270     }
00271 
00272     /* now close and nullify both filedescs */
00273     close_all();
00274 }
00275 
00276 int MidiDriver_TIMIDITY::connect_to_server(const char* hostname, const char* tcp_port) {
00277     int fd;
00278     struct addrinfo  hints;
00279     struct addrinfo *result, *rp;
00280 
00281     /* get all address(es) matching host and port */
00282     memset(&hints, 0, sizeof(struct addrinfo));
00283     hints.ai_socktype = SOCK_STREAM;
00284     hints.ai_family   = AF_UNSPEC; /* Allow IPv4 or IPv6 */
00285     if (getaddrinfo(hostname, tcp_port, &hints, &result) != 0) {
00286         warning("TiMidity: getaddrinfo: %s\n", strerror(errno));
00287         return -1;
00288     }
00289 
00290     /* Try all address structures we have got previously */
00291     for (rp = result; rp != NULL; rp = rp->ai_next) {
00292         if ((fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
00293             continue;
00294         if (connect(fd, rp->ai_addr, rp->ai_addrlen) != -1)
00295             break;
00296         ::close(fd);
00297     }
00298 
00299     freeaddrinfo(result);
00300 
00301     if (rp == NULL) {
00302         warning("TiMidity: Could not connect\n");
00303         return -1;
00304     }
00305 
00306     return fd;
00307 }
00308 
00309 char *MidiDriver_TIMIDITY::timidity_ctl_command(const char *fmt, ...) {
00310     /* XXX: I don't like this static buffer!!! */
00311     static char buff[BUFSIZ];
00312     va_list ap;
00313 
00314     if (fmt != NULL) {
00315         /* if argumends are present, write them to control connection */
00316         va_start(ap, fmt);
00317         int len = vsnprintf(buff, BUFSIZ-1, fmt, ap); /* leave one byte for \n */
00318         va_end(ap);
00319 
00320         /* add newline if needed */
00321         if (len > 0 && buff[len - 1] != '\n')
00322             buff[len++] = '\n';
00323 
00324         /* write command to control socket */
00325         if (write(_control_fd, buff, len) == -1) {
00326             warning("TiMidity: CONTROL WRITE FAILED (%s)", strerror(errno));
00327             // TODO: Disable output?
00328             //close_all();
00329         }
00330     }
00331 
00332     while (1) {
00333         /* read reply */
00334         if (fdgets(buff, sizeof(buff)) <= 0) {
00335             strcpy(buff, "Read error\n");
00336             break;
00337         }
00338 
00339         /* report errors from server */
00340         int status = atoi(buff);
00341         if (400 <= status && status <= 499) { /* Error of data stream */
00342             warning("TiMidity: error from server: %s", buff);
00343             continue;
00344         }
00345         break;
00346     }
00347 
00348     return buff;
00349 }
00350 
00351 void MidiDriver_TIMIDITY::timidity_meta_seq(int p1, int p2, int p3) {
00352     /* see _CHN_COMMON from soundcard.h; this is simplified
00353      * to just send seq to the server without any buffers,
00354      * delays and extra functions/macros */
00355     u_char seqbuf[8];
00356 
00357     seqbuf[0] = 0x92;
00358     seqbuf[1] = 0;
00359     seqbuf[2] = 0xff;
00360     seqbuf[3] = 0x7f;
00361     seqbuf[4] = p1;
00362     seqbuf[5] = p2;
00363     WRITE_UINT16(&seqbuf[6], p3);
00364 
00365     timidity_write_data(seqbuf, sizeof(seqbuf));
00366 }
00367 
00368 int MidiDriver_TIMIDITY::timidity_sync(int centsec) {
00369     char *res;
00370     int status;
00371     unsigned long sleep_usec;
00372 
00373     timidity_meta_seq(0x02, 0x00, centsec); /* Wait playout */
00374 
00375     /* Wait "301 Sync OK" */
00376     do {
00377         res = timidity_ctl_command(NULL);
00378         status = atoi(res);
00379 
00380         if (status != 301)
00381             warning("TiMidity: error: SYNC: %s", res);
00382 
00383     } while (status && status != 301);
00384 
00385     if (status != 301)
00386         return -1; /* error */
00387 
00388     sleep_usec = (unsigned long)(atof(res + 4) * 1000000);
00389 
00390     if (sleep_usec > 0)
00391         usleep(sleep_usec);
00392 
00393     return 0;
00394 }
00395 
00396 int MidiDriver_TIMIDITY::timidity_eot(void) {
00397     timidity_meta_seq(0x00, 0x00, 0); /* End of playing */
00398     return timidity_sync(0);
00399 }
00400 
00401 void MidiDriver_TIMIDITY::timidity_write_data(const void *buf, size_t nbytes) {
00402     /* nowhere to write... */
00403     if (_data_fd < 0)
00404         return;
00405 
00406     /* write, and disable everything if write failed */
00407     /* TODO: add reconnect? */
00408     if (write(_data_fd, buf, nbytes) == -1) {
00409         warning("TiMidity: DATA WRITE FAILED (%s), DISABLING MUSIC OUTPUT", strerror(errno));
00410         close_all();
00411     }
00412 }
00413 
00414 int MidiDriver_TIMIDITY::fdgets(char *buff, size_t buff_size) {
00415     int n, count, size;
00416     char *buff_endp = buff + buff_size - 1, *pbuff, *beg;
00417 
00418     count = _controlbuffer_count;
00419     size = _controlbuffer_size;
00420     pbuff = _controlbuffer;
00421     beg = buff;
00422     do {
00423         if (count == size) {
00424             if ((n = read(_control_fd, pbuff, BUFSIZ)) <= 0) {
00425                 *buff = '\0';
00426                 if (n == 0) {
00427                     _controlbuffer_count = _controlbuffer_size = 0;
00428                     return buff - beg;
00429                 }
00430                 return -1; /* < 0 error */
00431             }
00432             count = _controlbuffer_count = 0;
00433             size = _controlbuffer_size = n;
00434         }
00435         *buff++ = pbuff[count++];
00436     } while (*(buff - 1) != '\n' && buff != buff_endp);
00437 
00438     *buff = '\0';
00439     _controlbuffer_count = count;
00440 
00441     return buff - beg;
00442 }
00443 
00444 void MidiDriver_TIMIDITY::send(uint32 b) {
00445     unsigned char buf[256];
00446     int position = 0;
00447 
00448     switch (b & 0xF0) {
00449     case 0x80:
00450     case 0x90:
00451     case 0xA0:
00452     case 0xB0:
00453     case 0xE0:
00454         buf[position++] = SEQ_MIDIPUTC;
00455         buf[position++] = (unsigned char)b;
00456         buf[position++] = _device_num;
00457         buf[position++] = 0;
00458         buf[position++] = SEQ_MIDIPUTC;
00459         buf[position++] = (unsigned char)((b >> 8) & 0x7F);
00460         buf[position++] = _device_num;
00461         buf[position++] = 0;
00462         buf[position++] = SEQ_MIDIPUTC;
00463         buf[position++] = (unsigned char)((b >> 16) & 0x7F);
00464         buf[position++] = _device_num;
00465         buf[position++] = 0;
00466         break;
00467     case 0xC0:
00468     case 0xD0:
00469         buf[position++] = SEQ_MIDIPUTC;
00470         buf[position++] = (unsigned char)b;
00471         buf[position++] = _device_num;
00472         buf[position++] = 0;
00473         buf[position++] = SEQ_MIDIPUTC;
00474         buf[position++] = (unsigned char)((b >> 8) & 0x7F);
00475         buf[position++] = _device_num;
00476         buf[position++] = 0;
00477         break;
00478     default:
00479         warning("MidiDriver_TIMIDITY::send: unknown : %08x", (int)b);
00480         break;
00481     }
00482 
00483     timidity_write_data(buf, position);
00484 }
00485 
00486 void MidiDriver_TIMIDITY::sysEx(const byte *msg, uint16 length) {
00487     fprintf(stderr, "Timidity::sysEx\n");
00488     unsigned char buf[266*4];
00489     int position = 0;
00490     const byte *chr = msg;
00491 
00492     assert(length + 2 <= 266);
00493 
00494     buf[position++] = SEQ_MIDIPUTC;
00495     buf[position++] = 0xF0;
00496     buf[position++] = _device_num;
00497     buf[position++] = 0;
00498     for (; length; --length, ++chr) {
00499         buf[position++] = SEQ_MIDIPUTC;
00500         buf[position++] = (unsigned char) *chr & 0x7F;
00501         buf[position++] = _device_num;
00502         buf[position++] = 0;
00503     }
00504     buf[position++] = SEQ_MIDIPUTC;
00505     buf[position++] = 0xF7;
00506     buf[position++] = _device_num;
00507     buf[position++] = 0;
00508 
00509     timidity_write_data(buf, position);
00510 }
00511 
00512 
00513 // Plugin interface
00514 
00515 class TimidityMusicPlugin : public MusicPluginObject {
00516 public:
00517     const char *getName() const {
00518         return "TiMidity";
00519     }
00520 
00521     const char *getId() const {
00522         return "timidity";
00523     }
00524 
00525     MusicDevices getDevices() const;
00526     Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
00527 };
00528 
00529 MusicDevices TimidityMusicPlugin::getDevices() const {
00530     MusicDevices devices;
00531     devices.push_back(MusicDevice(this, "", MT_GM));
00532     return devices;
00533 }
00534 
00535 Common::Error TimidityMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
00536     *mididriver = new MidiDriver_TIMIDITY();
00537 
00538     return Common::kNoError;
00539 }
00540 
00541 //#if PLUGIN_ENABLED_DYNAMIC(TIMIDITY)
00542     //REGISTER_PLUGIN_DYNAMIC(TIMIDITY, PLUGIN_TYPE_MUSIC, TimidityMusicPlugin);
00543 //#else
00544     REGISTER_PLUGIN_STATIC(TIMIDITY, PLUGIN_TYPE_MUSIC, TimidityMusicPlugin);
00545 //#endif
00546 
00547 #endif // defined(USE_TIMIDITY)


Generated on Sat Jun 22 2019 05:00:38 for ResidualVM by doxygen 1.7.1
curved edge   curved edge