/* Pseudo /dev/sequencer of TiMidity * by Masanao Izumo * * This code for TiMidity++ v2.0.1. * * ChangeLog: * 2000-07-14 LD_PRELOAD=timidity-dynamic-io.so playmidi -e hoge.mid * 1999-09-20 timidity_sync(): Wait until sync is completed. * 1999-04-27 SNDCTL_SEQ_CTRLRATE: Query TiMidity server with TIMEBASE. * */ /* * Compile information * * gcc -DTIMIDITY_LOW_DELAY -fPIC -c timidity-dynamic-io.c * gcc -shared timidity-dynamic-io.o -o timidity-dynamic-io.so * * Run * LD_PRELOAD=./timidity-dynamic-io.so playmidi hoge.mid */ /*#define DEBUG*/ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include #include /* for gethostbyname */ #include #include #include #include #include /* Please modify include path if need */ #include #ifdef HAVE_SYS_AWE_VOICE_H #include #endif #ifndef SEQUENCER_DEV #define SEQUENCER_DEV "/dev/sequencer" #endif #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 /* TIMIDITY_ASYNC_MODE */ /* Default host & port */ #define TIMIDITY_HOST "127.0.0.1:7777" #define TIMIDITY_CONTROL_PORT 7777 #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff /* -1 return */ #endif /* INADDR_NONE */ struct fd_read_buffer { char buff[BUFSIZ]; /* count: beginning of read pointer * size: end of read pointer * fd: file descripter for input */ int count, size, fd; }; static struct fd_read_buffer control_fd; static int fdgets(char *buff, size_t buff_size, struct fd_read_buffer *p); static char *timidity_ctl_command(const char* fmt, ...); static struct synth_info ext_synth_info[] = { { "TiMidity server", /* name */ 0, /* device */ SYNTH_TYPE_MIDI, /* synth_type */ SAMPLE_TYPE_BASIC, /* synth_subtype */ 0, /* perc_mode */ 36, /* nr_voices */ 0, /* nr_drums */ 128, /* instr_bank_size */ 0 /* capabilities */ }, #ifdef HAVE_SYS_AWE_VOICE_H { "TiMidity server", /* name */ 1, /* device */ SYNTH_TYPE_SAMPLE, /* synth_type */ SAMPLE_TYPE_AWE32, /* synth_subtype */ 0, /* perc_mode */ 36, /* nr_voices */ 0, /* nr_drums */ 128, /* instr_bank_size */ 0 /* capabilities */ }, #endif }; static int seqfd = -1, sync_mode = 0; //SEQ_USE_EXTBUF(); SEQ_DEFINEBUF(128); void seqbuf_dump(void) { if(_seqbufptr) if(write(seqfd, _seqbuf, _seqbufptr) == -1) { perror ("write " SEQUENCER_DEV); exit(-1); } _seqbufptr = 0; } static void timidity_seqbuf_dump(void) { if(_seqbufptr) if(write(seqfd, _seqbuf, _seqbufptr) == -1) { perror ("write " SEQUENCER_DEV); exit(-1); } _seqbufptr = 0; } static void timidity_meta_seq(int p1, int p2, int p3) { _CHN_COMMON(0, 0xff, 0x7f, p1, p2, p3); SEQ_DUMPBUF(); } static int 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) fprintf(stderr, "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; } static int timidity_eot(void) { timidity_meta_seq(0x00, 0x00, 0); /* End of playing */ return timidity_sync(0); } /* Return internet IP address in network byte order */ static unsigned int host_ip_address(const char* address) { unsigned int addr; struct hostent *hp; if((addr = inet_addr(address)) != INADDR_NONE) return addr; if((hp = gethostbyname(address)) == NULL) { fprintf(stderr, "Unknown host name: %s\n", address); exit(1); } memcpy(&addr, hp->h_addr, hp->h_length); return addr; } static int connect_to_server(const char* hostname, unsigned short tcp_port) { int fd, len; struct sockaddr_in in; unsigned int addr; struct hostent *hp; if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); exit(1); } memset(&in, 0, sizeof(in)); in.sin_family = AF_INET; in.sin_port = htons(tcp_port); addr = host_ip_address(hostname); memcpy(&in.sin_addr, &addr, 4); hp = gethostbyaddr((const char *)&in.sin_addr, 4, AF_INET); if(hp != NULL) fprintf(stderr, "Try to connect to %s (addr=%s, port=%d)\n", hp->h_name, inet_ntoa(in.sin_addr), tcp_port); else fprintf(stderr, "Try to connect to %s (port=%d)\n", inet_ntoa(in.sin_addr), tcp_port); alarm(2 * 60); if(connect(fd, (struct sockaddr *)&in, sizeof(in)) < 0) { perror("connect"); return -1; } alarm(0); len = sizeof(in); if(getsockname(fd, (struct sockaddr *)&in, &len) < 0) perror("getsockname"); return fd; } static int fdgets(char *buff, size_t buff_size, struct fd_read_buffer *p) { int n, len, count, size, fd; char *buff_endp = buff + buff_size - 1, *pbuff, *beg; fd = p->fd; if(buff_size == 0) return 0; else if(buff_size == 1) /* buff == buff_endp */ { *buff = '\0'; return 0; } len = 0; count = p->count; size = p->size; pbuff = p->buff; beg = buff; do { if(count == size) { if((n = read(fd, pbuff, BUFSIZ)) <= 0) { *buff = '\0'; if(n == 0) { p->count = p->size = 0; return buff - beg; } return -1; /* < 0 error */ } count = p->count = 0; size = p->size = n; } *buff++ = pbuff[count++]; } while(*(buff - 1) != '\n' && buff != buff_endp); *buff = '\0'; p->count = count; return buff - beg; } static char *timidity_ctl_command(const char* fmt, ...) { static char buff[BUFSIZ]; int status, len; va_list ap; if(fmt != NULL) { va_start(ap, fmt); vsprintf(buff, fmt, ap); va_end(ap); len = strlen(buff); if(len > 0 && buff[len - 1] != '\n') buff[len++] = '\n'; write(control_fd.fd, buff, len); } while(1) { if(fdgets(buff, sizeof(buff), &control_fd) <= 0) { strcpy(buff, "Read error\n"); break; } status = atoi(buff); if(400 <= status && status <= 499) /* Error of data stream */ { fputs(buff, stderr); continue; } break; } return buff; } #ifdef __cplusplus extern "C" { #endif int open(const char *path, int oflag, ...) { char *res; int fd, i, data_port; char timidity_host[128]; int timidity_port; if(strcmp(path, SEQUENCER_DEV) != 0) { int ret; va_list ap; mode_t mode; va_start(ap, oflag); mode = va_arg(ap, mode_t); ret = syscall(SYS_open, path, oflag, mode); va_end(ap); return ret; } #ifdef DEBUG fprintf(stderr, "open\n"); #endif if(seqfd != -1) return seqfd; if((res = getenv("TIMIDITY_HOST")) == NULL) res = TIMIDITY_HOST; strncpy(timidity_host, res, sizeof(timidity_host)); if((res = strrchr(timidity_host, ':')) != NULL) { *res++ = '\0'; timidity_port = atoi(res); } else timidity_port = TIMIDITY_CONTROL_PORT; memset(&control_fd, 0, sizeof(control_fd)); control_fd.fd = connect_to_server(timidity_host, timidity_port); res = timidity_ctl_command(NULL); if(atoi(res) != 220) { fprintf(stderr, "Can't connect timidity: (host=%s, port=%d)\n", timidity_host, timidity_port); return -1; } fputs(res, stdout); res = timidity_ctl_command("SETBUF %f %f", BUF_LOW_SYNC, BUF_HIGH_SYNC); if(atoi(res) != 200) fprintf(stderr, "WARNING: %s", res); i = 1; if(*(char *)&i == 1) res = timidity_ctl_command("OPEN lsb"); else res = timidity_ctl_command("OPEN msb"); if(atoi(res) != 200) { fprintf(stderr, "Can't open timidity data connection: %s", res); syscall(SYS_close, control_fd.fd); return -1; } data_port = atoi(res + 4); fd = connect_to_server(timidity_host, data_port); 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); syscall(SYS_close, control_fd.fd); return -1; } seqfd = fd; return fd; } int close(int fd) { char *res; if(fd == seqfd) { #ifdef DEBUG fprintf(stderr, "close\n"); #endif timidity_eot(); timidity_sync(0); do { res = timidity_ctl_command("QUIT"); } while(atoi(res) && atoi(res) != 302); syscall(SYS_close, control_fd.fd); seqfd = -1; } return syscall(SYS_close, fd); } static double get_time(void) { struct timeval tv; struct timezone dmy; #ifdef GETTIMEOFDAY_ONE_ARGUMENT gettimeofday(&tv); #else gettimeofday(&tv, &dmy); #endif return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0 ; } int ioctl(int fildes, int request, ...) { void *arg; va_list ap; char *res; va_start(ap, request); arg = va_arg(ap, void *); va_end(ap); if(fildes != seqfd) return syscall(SYS_ioctl, fildes, request, arg); switch(request) { case SNDCTL_SEQ_RESET: #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_SEQ_RESET\n"); #endif timidity_eot(); timidity_meta_seq(0x01, 0x00, 0); /* TiMidity reset */ return 0; case SNDCTL_SEQ_SYNC: { static double last_time; double t; #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_SEQ_SYNC\n"); #endif if(sync_mode) return 0; t = get_time(); if(t - last_time < 0.1) return 0; last_time = t; sync_mode = 1; timidity_sync((int)(BUF_HIGH_SYNC * 100)); sync_mode = 0; } return 0; case SNDCTL_SYNTH_INFO: #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_SYNTH_INFO\n"); #endif memcpy(arg, &ext_synth_info[ ((struct synth_info *)arg)->device ], sizeof(struct synth_info)); return 0; case SNDCTL_SEQ_CTRLRATE: #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_SEQ_CTRLRATE\n"); #endif res = timidity_ctl_command("TIMEBASE"); if(atoi(res) != 200) return -1; *(int *)arg = atoi(res + 4); return 0; case SNDCTL_SEQ_TESTMIDI: #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_SEQ_TESTMIDI\n"); #endif return 0; case SNDCTL_SEQ_RESETSAMPLES: #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_SEQ_RESETSAMPLES\n"); #endif return 0; case SNDCTL_SEQ_NRSYNTHS: #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_SEQ_NRSYNTHS\n"); #endif *(int *)arg = sizeof(ext_synth_info)/sizeof(ext_synth_info[0]); return 0; case SNDCTL_SEQ_NRMIDIS: #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_SEQ_NRMIDIS\n"); #endif *(int *)arg = 1; return 0; case SNDCTL_MIDI_INFO: #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_MIDI_INFO\n"); #endif return -1; case SNDCTL_SYNTH_MEMAVL: #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_SYNTH_MEMAVL\n"); #endif return -1; case SNDCTL_FM_4OP_ENABLE: #ifdef DEBUG fprintf(stderr, "ioctl SNDCTL_FM_40P_ENABLE\n"); #endif return -1; default: #ifdef DEBUG fprintf(stderr, "ioctl UNKNOWN : %08x\n", request); #endif return -1; } } #ifdef __cplusplus } #endif