/* Pseudo /dev/sequencer of TiMidity * This code for TiMidity++ v2.0.0. */ #undef open #undef ioctl #undef close #include "playmidi.h" #include #include #include #include #include /* for gethostbyname */ #include #include #include SEQ_USE_EXTBUF(); #define TIMIDITY_HOST "127.0.0.1" #define TIMIDITY_CONTROL_PORT 7777 #define TIMIDITY_SYNC_LOWER 0.8 #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, /* sunth_subtype */ 0, /* perc_mode */ 36, /* nr_voices */ 0, /* nr_drums */ 128, /* instr_bank_size */ 0 /* capabilities */ }, #if 0 /* Bend range is not correctly worked */ { "TiMidity server", /* name */ 1, /* device */ SYNTH_TYPE_SAMPLE, /* synth_type */ SAMPLE_TYPE_AWE32, /* sunth_subtype */ 0, /* perc_mode */ 36, /* nr_voices */ 0, /* nr_drums */ 128, /* instr_bank_size */ 0 /* capabilities */ }, #endif }; void timidity_sync(int tick) { char *res; int status; unsigned long sleep_usec; _CHN_COMMON(0, 0xff, 0x7f, 0x02, 0x00, tick); /* Wait playout */ SEQ_DUMPBUF(); /* 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) { sleep_usec = (unsigned long)(atof(res + 4) * 1000000); if(sleep_usec > 0) { usleep(sleep_usec); } } } 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 timidity_ioctl(int fildes, int request, void *arg) { char *res; switch(request) { case SNDCTL_SEQ_NRSYNTHS: *(int *)arg = sizeof(ext_synth_info)/sizeof(ext_synth_info[0]); return 0; case SNDCTL_SYNTH_INFO: memcpy(arg, &ext_synth_info[ ((struct synth_info *)arg)->device ], sizeof(struct synth_info)); return 0; case SNDCTL_SEQ_NRMIDIS: *(int *)arg = 1; return 0; case SNDCTL_SEQ_RESET: _CHN_COMMON(0, 0xff, 0x2f, 0x00, 0x00, 0x0000); /* End of playing */ _CHN_COMMON(0, 0xff, 0x7f, 0x01, 0x00, 0x0000); /* TiMidity reset */ SEQ_DUMPBUF(); timidity_sync(0); return 0; case SNDCTL_SEQ_RESETSAMPLES: return 0; case SNDCTL_SEQ_SYNC: { static double last_time; double t, sec; t = get_time(); if(t - last_time < 0.1) return 0; last_time = t; timidity_sync(60); } return 0; case SNDCTL_FM_4OP_ENABLE: case SNDCTL_SYNTH_MEMAVL: return -1; } return ioctl(fildes, request, arg); } /* 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((unsigned 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; } int timidity_open(const char *path, int oflag, int mode) { char *res; int fd, i, data_port; extern int graphics; if(strcmp(path, SEQUENCER_DEV) != 0) return open(path, oflag, mode); memset(&control_fd, 0, sizeof(control_fd)); control_fd.fd = connect_to_server(TIMIDITY_HOST, TIMIDITY_CONTROL_PORT); res = timidity_ctl_command(NULL); if(atoi(res) != 220) { fprintf(stderr, "Can't connect timidity: (host=%s, port=%d)\n", TIMIDITY_HOST, TIMIDITY_CONTROL_PORT); return -1; } fputs(res, stdout); res = timidity_ctl_command("SETBUF 0.4 0.6"); if(atoi(res) != 200) fprintf(stderr, "Warning: %s", res); if(!graphics) { /* Voice optimaization for non-real time. */ res = timidity_ctl_command("AUTOREDUCE on 400"); 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); 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); close(control_fd.fd); return -1; } 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; } void timidity_close(int fd) { extern int seqfd; if(fd == seqfd) { _CHN_COMMON(0, 0xff, 0x2f, 0x00, 0x00, 0x0000); /* End of playing */ timidity_sync(0); } close(fd); } #if 0 /* Not used */ void timidity_gus_load(int pgm) { int dr, bank, prog; char *res; if(pgm < 0) return; if(patchloaded[pgm]) return; if(pgm >= 128) res = timidity_ctl_command("PATCH drumset 0 %d", pgm - 128); else res = timidity_ctl_command("PATCH bank 0 %d", pgm); if(atoi(res) != 200) { fputs(res, stderr); patchloaded[pgm] = -1; } else patchloaded[pgm] = 1; } #endif