diff options
author | Max Horn | 2007-11-02 16:01:52 +0000 |
---|---|---|
committer | Max Horn | 2007-11-02 16:01:52 +0000 |
commit | 8eb73f269492e5123e86236f5398a14e6aa991b5 (patch) | |
tree | 3de5e82f071d3278a73c8f0894aff941997acfb8 /backends/midi/timidity.cpp | |
parent | 231a7875bdae36a9e17ae8a51b870e37244fa02e (diff) | |
download | scummvm-rg350-8eb73f269492e5123e86236f5398a14e6aa991b5.tar.gz scummvm-rg350-8eb73f269492e5123e86236f5398a14e6aa991b5.tar.bz2 scummvm-rg350-8eb73f269492e5123e86236f5398a14e6aa991b5.zip |
Patch #1815526: Add TiMidity++ MIDI server support
svn-id: r29377
Diffstat (limited to 'backends/midi/timidity.cpp')
-rw-r--r-- | backends/midi/timidity.cpp | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/backends/midi/timidity.cpp b/backends/midi/timidity.cpp new file mode 100644 index 0000000000..132ebd7c49 --- /dev/null +++ b/backends/midi/timidity.cpp @@ -0,0 +1,512 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +/* + * Output to TiMidity++ MIDI server support + * by Dmitry Marakasov <amdmi3@amdmi3.ru> + * based on: + * - Raw output support (seq.cpp) by Michael Pearce + * - Pseudo /dev/sequencer of TiMidity (timidity-io.c) + * by Masanao Izumo <mo@goice.co.jp> + * - sys/soundcard.h by Hannu Savolainen (got from my FreeBSD + * distribution, for which it was modified by Luigi Rizzo) + * + */ + +#if defined (UNIX) + +#include "sound/mpu401.h" +#include "common/util.h" + +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <netdb.h> /* for gethostbyname */ +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdarg.h> +#include <stdlib.h> +#include <errno.h> + +#define SEQ_MIDIPUTC 5 + +#define TIMIDITY_LOW_DELAY + +#ifdef TIMIDITY_LOW_DELAY +#define BUF_LOW_SYNC 0.1 +#define BUF_HIGH_SYNC 0.15 +#else +#define BUF_LOW_SYNC 0.4 +#define BUF_HIGH_SYNC 0.8 +#endif + +/* default host & port */ +#define DEFAULT_TIMIDITY_HOST "127.0.0.1" +#define DEFAULT_TIMIDITY_PORT 7777 + +class MidiDriver_TIMIDITY : public MidiDriver_MPU401 { +public: + MidiDriver_TIMIDITY(); + + int open(); + void close(); + void send(uint32 b); + void sysEx(const byte *msg, uint16 length); + +private: + /* standart routine to extract ip address from a string */ + in_addr_t host_to_addr(const char* address); + + /* creates a tcp connection to TiMidity server, returns filedesc (like open()) */ + int connect_to_server(const char* hostname, unsigned short tcp_port); + + /* send command to the server; printf-like; returns reply string */ + char *timidity_ctl_command(const char *fmt, ...); + + /* timidity data socket-related stuff */ + void timidity_meta_seq(int p1, int p2, int p3); + int timidity_sync(int centsec); + int timidity_eot(); + + /* write() analogue for any midi data */ + void timidity_write_data(const void *buf, size_t nbytes); + + /* get single line of server reply on control connection */ + int fdgets(char *buff, size_t buff_size); + + /* teardown connection to server */ + void teardown(); + + /* close (if needed) and nullify both control and data filedescs */ + void close_all(); + +private: + bool _isOpen; + int _device_num; + + int _control_fd; + int _data_fd; + + /* buffer for partial data read from _control_fd - from timidity-io.c, see fdgets() */ + char _controlbuffer[BUFSIZ]; + int _controlbuffer_count; /* beginning of read pointer */ + int _controlbuffer_size; /* end of read pointer */ +}; + +MidiDriver_TIMIDITY::MidiDriver_TIMIDITY() { + _isOpen = false; + _device_num = 0; + + /* init fd's */ + _control_fd = _data_fd = -1; + + /* init buffer for control connection */ + _controlbuffer_count = _controlbuffer_size = 0; +} + +int MidiDriver_TIMIDITY::open() { + char *res; + char timidity_host[MAXHOSTNAMELEN]; + int timidity_port, data_port, i; + + /* count ourselves open */ + if (_isOpen) + return MERR_ALREADY_OPEN; + _isOpen = true; + + /* get server hostname; if not specified in env, use default */ + if ((res = getenv("TIMIDITY_HOST")) == NULL) + strncpy(timidity_host, DEFAULT_TIMIDITY_HOST, MAXHOSTNAMELEN); + else + strncpy(timidity_host, res, sizeof(timidity_host)); + + timidity_host[sizeof(timidity_host) - 1] = '\0'; + + /* extract control port */ + if ((res = strrchr(timidity_host, ':')) != NULL) { + *res++ = '\0'; + timidity_port = atoi(res); + } else { + timidity_port = DEFAULT_TIMIDITY_PORT; + } + + /* + * create control connection to the server + */ + if ((_control_fd = connect_to_server(timidity_host, timidity_port)) < 0) { + warning("TiMidity: can't open control connection (host=%s, port=%d)", timidity_host, timidity_port); + return -1; + } + + /* should read greeting issued by server upon connect: + * "220 TiMidity++ v2.13.2 ready)" */ + res = timidity_ctl_command(NULL); + if (atoi(res) != 220) { + warning("TiMidity: bad response from server (host=%s, port=%d): %s", timidity_host, timidity_port, res); + close_all(); + return -1; + } + + /* + * setup buf and prepare data connection + */ + /* should read: "200 OK" */ + res = timidity_ctl_command("SETBUF %f %f", BUF_LOW_SYNC, BUF_HIGH_SYNC); + if (atoi(res) != 200) + warning("TiMidity: bad reply for SETBUF command: %s", res); + + /* should read something like "200 63017 is ready acceptable", + * where 63017 is port for data connection */ + i = 1; + if (*(char *)&i == 1) + res = timidity_ctl_command("OPEN lsb"); + else + res = timidity_ctl_command("OPEN msb"); + + if (atoi(res) != 200) { + warning("TiMidity: bad reply for OPEN command: %s", res); + close_all(); + return -1; + } + + /* + * open data connection + */ + data_port = atoi(res + 4); + if ((_data_fd = connect_to_server(timidity_host, data_port)) < 0) { + warning("TiMidity: can't open data connection (host=%s, port=%d)", timidity_host, data_port); + close_all(); + return -1; + } + + /* should read message issued after connecting to data port: + * "200 Ready data connection" */ + res = timidity_ctl_command(NULL); + if (atoi(res) != 200) { + fprintf(stderr, "Can't connect timidity: %s\t(host=%s, port=%d)\n", res, timidity_host, data_port); + close_all(); + return -1; + } + + /* + * From seq.cpp + */ + if (getenv("SCUMMVM_MIDIPORT")) + _device_num = atoi(getenv("SCUMMVM_MIDIPORT")); + + return 0; +} + +void MidiDriver_TIMIDITY::close() { + teardown(); + + MidiDriver_MPU401::close(); + _isOpen = false; +} + +void MidiDriver_TIMIDITY::close_all() { + if (_control_fd >= 0) + ::close(_control_fd); + + if (_data_fd >= 0) + ::close(_data_fd); + + _control_fd = _data_fd = -1; +} + +void MidiDriver_TIMIDITY::teardown() { + char *res; + + /* teardown connection to server (see timidity-io.c) if it + * is initialized */ + if (_data_fd >= 0 && _control_fd >= 0) { + timidity_eot(); + timidity_sync(0); + + /* scroll through all "302 Data connection is (already) closed" + * messages till we reach something like "200 Bye" */ + do { + res = timidity_ctl_command("QUIT"); + } while(*res && atoi(res) && atoi(res) != 302); + } + + /* now close and nullify both filedescs */ + close_all(); +} + +in_addr_t MidiDriver_TIMIDITY::host_to_addr(const char* address) { + in_addr_t addr; + struct hostent *hp; + + /* first check if IP address is given (like 127.0.0.1)*/ + if ((addr = inet_addr(address)) != INADDR_NONE) + return addr; + + /* if not, try to resolve a hostname */ + if ((hp = gethostbyname(address)) == NULL) { + warning("TiMidity: unknown hostname: %s\n", address); + return INADDR_NONE; + } + + memcpy(&addr, hp->h_addr, (int)sizeof(in_addr_t) <= hp->h_length ? sizeof(in_addr_t) : hp->h_length); + + return addr; +} + +int MidiDriver_TIMIDITY::connect_to_server(const char* hostname, unsigned short tcp_port) { + int fd; + struct sockaddr_in in; + unsigned int addr; + + /* create socket */ + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + warning("TiMidity: socket(): %s", strerror(errno)); + return -1; + } + + /* connect */ + memset(&in, 0, sizeof(in)); + in.sin_family = AF_INET; + in.sin_port = htons(tcp_port); + addr = host_to_addr(hostname); + memcpy(&in.sin_addr, &addr, 4); + + if (connect(fd, (struct sockaddr *)&in, sizeof(in)) < 0) { + warning("TiMidity: connect(): %s", strerror(errno)); + return -1; + } + + return fd; +} + +char *MidiDriver_TIMIDITY::timidity_ctl_command(const char *fmt, ...) { + /* XXX: I don't like this static buffer!!! */ + static char buff[BUFSIZ]; + int status, len; + va_list ap; + + if (fmt != NULL) { + /* if argumends are present, write them to control connection */ + va_start(ap, fmt); + len = vsnprintf(buff, BUFSIZ-1, fmt, ap); /* leave one byte for \n */ + va_end(ap); + + /* add newline if needed */ + if(len > 0 && buff[len - 1] != '\n') + buff[len++] = '\n'; + + /* write command to control socket */ + write(_control_fd, buff, len); + } + + while(1) { + /* read reply */ + if (fdgets(buff, sizeof(buff)) <= 0) { + strcpy(buff, "Read error\n"); + break; + } + + /* report errors from server */ + status = atoi(buff); + if (400 <= status && status <= 499) { /* Error of data stream */ + warning("TiMidity: error from server: %s", buff); + continue; + } + break; + } + + return buff; +} + +void MidiDriver_TIMIDITY::timidity_meta_seq(int p1, int p2, int p3) { + /* see _CHN_COMMON from soundcard.h; this is simplified + * to just send seq to the server without any buffers, + * delays and extra functions/macros */ + u_char seqbuf[8]; + + seqbuf[0] = 0x92; + seqbuf[1] = 0; + seqbuf[2] = 0xff; + seqbuf[3] = 0x7f; + seqbuf[4] = p1; + seqbuf[5] = p2; + *(short *)&seqbuf[6] = p3; + + timidity_write_data(seqbuf, sizeof(seqbuf)); +} + +int MidiDriver_TIMIDITY::timidity_sync(int centsec) { + char *res; + int status; + unsigned long sleep_usec; + + timidity_meta_seq(0x02, 0x00, centsec); /* Wait playout */ + + /* Wait "301 Sync OK" */ + do { + res = timidity_ctl_command(NULL); + status = atoi(res); + + if(status != 301) + warning("TiMidity: error: SYNC: %s", res); + + } while(status && status != 301); + + if(status != 301) + return -1; /* error */ + + sleep_usec = (unsigned long)(atof(res + 4) * 1000000); + + if(sleep_usec > 0) + usleep(sleep_usec); + + return 0; +} + +int MidiDriver_TIMIDITY::timidity_eot(void) { + timidity_meta_seq(0x00, 0x00, 0); /* End of playing */ + return timidity_sync(0); +} + +void MidiDriver_TIMIDITY::timidity_write_data(const void *buf, size_t nbytes) { + /* nowhere to write... */ + if (_data_fd < 0) + return; + + /* write, and disable everything if write failed */ + /* TODO: add reconnect? */ + if (write(_data_fd, buf, nbytes) == -1) { + warning("TiMidity: DATA WRITE FAILED (%s), DISABLING MUSIC OUTPUT", strerror(errno)); + close_all(); + } +} + +int MidiDriver_TIMIDITY::fdgets(char *buff, size_t buff_size) { + int n, len, count, size; + char *buff_endp = buff + buff_size - 1, *pbuff, *beg; + + len = 0; + count = _controlbuffer_count; + size = _controlbuffer_size; + pbuff = _controlbuffer; + beg = buff; + do { + if (count == size) { + if ((n = read(_control_fd, pbuff, BUFSIZ)) <= 0) { + *buff = '\0'; + if (n == 0) { + _controlbuffer_count = _controlbuffer_size = 0; + return buff - beg; + } + return -1; /* < 0 error */ + } + count = _controlbuffer_count = 0; + size = _controlbuffer_size = n; + } + *buff++ = pbuff[count++]; + } while(*(buff - 1) != '\n' && buff != buff_endp); + + *buff = '\0'; + _controlbuffer_count = count; + + return buff - beg; +} + +void MidiDriver_TIMIDITY::send(uint32 b) { + unsigned char buf[256]; + int position = 0; + + switch (b & 0xF0) { + case 0x80: + case 0x90: + case 0xA0: + case 0xB0: + case 0xE0: + buf[position++] = SEQ_MIDIPUTC; + buf[position++] = (unsigned char)b; + buf[position++] = _device_num; + buf[position++] = 0; + buf[position++] = SEQ_MIDIPUTC; + buf[position++] = (unsigned char)((b >> 8) & 0x7F); + buf[position++] = _device_num; + buf[position++] = 0; + buf[position++] = SEQ_MIDIPUTC; + buf[position++] = (unsigned char)((b >> 16) & 0x7F); + buf[position++] = _device_num; + buf[position++] = 0; + break; + case 0xC0: + case 0xD0: + buf[position++] = SEQ_MIDIPUTC; + buf[position++] = (unsigned char)b; + buf[position++] = _device_num; + buf[position++] = 0; + buf[position++] = SEQ_MIDIPUTC; + buf[position++] = (unsigned char)((b >> 8) & 0x7F); + buf[position++] = _device_num; + buf[position++] = 0; + break; + default: + warning("MidiDriver_TIMIDITY::send: unknown : %08x", (int)b); + break; + } + + timidity_write_data(buf, position); +} + +void MidiDriver_TIMIDITY::sysEx(const byte *msg, uint16 length) { + fprintf(stderr, "Timidity::sysEx\n"); + unsigned char buf[1024]; + int position = 0; + const byte *chr = msg; + + assert(length + 2 <= 256); + + buf[position++] = SEQ_MIDIPUTC; + buf[position++] = 0xF0; + buf[position++] = _device_num; + buf[position++] = 0; + for (; length; --length, ++chr) { + buf[position++] = SEQ_MIDIPUTC; + buf[position++] = (unsigned char) *chr & 0x7F; + buf[position++] = _device_num; + buf[position++] = 0; + } + buf[position++] = SEQ_MIDIPUTC; + buf[position++] = 0xF7; + buf[position++] = _device_num; + buf[position++] = 0; + + timidity_write_data(buf, position); +} + +MidiDriver *MidiDriver_TIMIDITY_create() { + return new MidiDriver_TIMIDITY(); +} + +#endif // defined (UNIX) |