1 /************************************************************************
2  * toy.c - start of midi recording package.  Will take input from
3  * /dev/sequencer and layer with an existing /dev/sequencer dump file,
4  * saving the result as a new /dev/sequencer dump file.  In short, you
5  * must have hardware midi devices to use this "toy".
6  *
7  * This program is an experiment in midi recording to solve some timing
8  * and input/output issues that I've had while writing my midi studio
9  * package (basically to test how to best use select() on /dev/sequencer).
10  *
11  * This code was written by by Nathan Laredo (laredo@gnu.ai.mit.edu)
12  * Source code may be freely distributed in unmodified form.
13  *************************************************************************/
14 #include <stdio.h>
15 #ifndef __FreeBSD__
16 #include <getopt.h>
17 #endif
18 #include <fcntl.h>
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <sys/time.h>
22 #ifndef __FreeBSD__
23 #include <sys/soundcard.h>
24 #else
25 #include <machine/soundcard.h>
26 #endif
27 #include <sys/ioctl.h>
28 
29 
30 #define SEQUENCER_DEV	"/dev/sequencer"
31 #define SEQUENCERBLOCKSIZE	128
32 
33 /*
34  * The following are if you have more than one midi device on
35  * your system as I do.   My Roland piano is on the 2nd midi port (GUS), and
36  * my Korg daughterboard is on the 1st midi port (SB16).
37  * Input will be taken from any midi device known by the kernel sequencer.
38  * device.
39  */
40 
41 #define OUT_DEV 0
42 
43 SEQ_DEFINEBUF(SEQUENCERBLOCKSIZE);
44 unsigned char inputbuf[SEQUENCERBLOCKSIZE];
45 unsigned char outputbuf[SEQUENCERBLOCKSIZE];
46 int seqfd, outfile, infile;
47 
48 /* indexed by high nibble of command */
49 int cmdlen[16] =
50 {0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 2, 0};
51 
52 void seqbuf_dump()
53 {
54     if (_seqbufptr) {
55 	if (write(seqfd, _seqbuf, _seqbufptr) == -1) {
56 	    perror(SEQUENCER_DEV);
57 	    exit(-1);
58 	}
59 	if (write(outfile, _seqbuf, _seqbufptr) == -1) {
60 	    perror("write");
61 	    exit(-1);
62 	}
63     }
64     _seqbufptr = 0;
65 }
66 
67 void seq_addevent(s, c)
68 unsigned char *s;
69 int c;
70 {
71     int i;
72 
73     if (*s == SEQ_SYNCTIMER)	/* we want only one of these messages */
74 	return;
75     _SEQ_NEEDBUF(c);
76     for (i = 0; i < c; i++)
77 	_seqbuf[_seqbufptr + i] = s[i];
78     _SEQ_ADVBUF(c);
79 }
80 
81 int seqread(f, buf)
82 int f;
83 unsigned char *buf;
84 {
85     return read(f, buf, 4);
86 }
87 
88 
89 int main(argc, argv)
90 int argc;
91 char **argv;
92 {
93     extern char *optarg;
94     extern int optind;
95     int i, transpose = 0, channel = 0, program = 0, error = 0;
96     unsigned char mid[8], imid[8], cmd = 0, icmd = 0;
97     unsigned int oticks = 0, ticks = 0, db = 0, idb = 0, wait = 0;
98     fd_set rdfs;
99     struct timeval tv, start, now, want;
100 
101     while ((i = getopt(argc, argv, "c:p:wt:")) != -1)
102 	switch (i) {
103 	case 'c':
104 	    channel = atoi(optarg);
105 	    break;
106 	case 'p':
107 	    program = atoi(optarg);
108 	    break;
109 	case 'w':
110 	    wait++;
111 	    break;
112 	case 't':
113 	    transpose = atoi(optarg);
114 	    break;
115 	default:
116 	    error++;
117 	    break;
118 	}
119 
120     if (error || argc - optind != 2) {
121 	fprintf(stderr, "usage: %s [-t semitones]"
122 		" [-c channel] [-p program] [-wait] "
123 		"inputfile.seq outputfile.seq\n", argv[0]);
124 	exit(1);
125     }
126     if ((seqfd = open(SEQUENCER_DEV, O_RDWR, 0)) < 0) {
127 	perror("open " SEQUENCER_DEV);
128 	exit(-1);
129     }
130     if ((infile = open(argv[optind], O_RDONLY, 0)) < 0) {
131 	perror(argv[optind]);
132 	exit(-1);
133     }
134     if ((outfile = open(argv[optind + 1], O_WRONLY | O_CREAT | O_TRUNC, 0666))
135 	< 0) {
136 	perror(argv[optind + 1]);
137 	exit(-1);
138     }
139     ioctl(seqfd, SNDCTL_SEQ_RESET);
140     if (program >= 1 && program <= 128) {
141 	if (channel < 1 || channel > 16)
142 	    channel = 1;
143 	SEQ_MIDIOUT(OUT_DEV, 0xc0 | (channel - 1));
144 	SEQ_MIDIOUT(OUT_DEV, program - 1);
145     }
146     /* extra byte for converting 3-byte timer ticks */
147     inputbuf[4] = 0;
148     outputbuf[4] = 0;
149     if (wait) {	/* wait for midi input to start */
150 	FD_ZERO(&rdfs);
151 	FD_SET(seqfd, &rdfs);
152 	select(FD_SETSIZE, &rdfs, NULL, NULL, NULL);
153 	seqread(seqfd, outputbuf);	/* trash time stamp (hopefully) */
154     }
155     SEQ_START_TIMER();
156     SEQ_DUMPBUF();
157     gettimeofday(&start, NULL);
158     while (ticks < 0xffffff) {
159 	FD_ZERO(&rdfs);
160 	FD_SET(infile, &rdfs);
161 	tv.tv_sec = tv.tv_usec = 0;	/* no wait */
162 	if (!select(FD_SETSIZE, &rdfs, NULL, NULL, &tv)) {
163 	    /* wait forever for more input -- mark end of input */
164 	    inputbuf[0] = SEQ_WAIT;
165 	    inputbuf[1] = inputbuf[2] = inputbuf[3] = 0xff;
166 	} else if (seqread(infile, inputbuf) < 4) {
167 	    inputbuf[0] = SEQ_WAIT;
168 	    inputbuf[1] = inputbuf[2] = inputbuf[3] = 0xff;
169 	}
170 	if (inputbuf[0] == SEQ_WAIT)
171 	    ticks = (*(unsigned int *) &inputbuf[1]);
172 	want.tv_sec = ticks / 100 + start.tv_sec;
173 	want.tv_usec = (ticks % 100) * 10000 + start.tv_usec;
174 	if (want.tv_usec >= 1000000) {
175 	    want.tv_usec -= 1000000;
176 	    want.tv_sec++;
177 	}
178 	if (*inputbuf == SEQ_MIDIPUTC) {
179 	    if (inputbuf[1] & 0x80)
180 		icmd = imid[idb = 0] = inputbuf[1];
181 	    else
182 		imid[idb] = inputbuf[1];
183 	    idb++;
184 	    if (idb == cmdlen[icmd >> 4] + 1) {
185 		fprintf(stderr, ">%8d ", ticks);
186 		for (i = 0; i < idb; i++) {
187 		    fprintf(stderr, "%02x", imid[i]);
188 		    SEQ_MIDIOUT(OUT_DEV, imid[i]);
189 		}
190 		fprintf(stderr, "\n");
191 		idb = 1;
192 	    }
193 	} else {
194 	    gettimeofday(&now, NULL);
195 	    while (timercmp(&now, &want, <)) {
196 		tv.tv_sec = want.tv_sec - now.tv_sec;
197 		tv.tv_usec = want.tv_usec - now.tv_usec;
198 		if (tv.tv_usec < 0) {
199 		    tv.tv_usec += 1000000;
200 		    tv.tv_sec--;
201 		}
202 		FD_ZERO(&rdfs);
203 		FD_SET(seqfd, &rdfs);
204 		if (select(FD_SETSIZE, &rdfs, NULL, NULL, &tv)) {
205 		    seqread(seqfd, outputbuf);
206 		    if (outputbuf[0] == SEQ_WAIT) {
207 			oticks = (*(unsigned int *) &outputbuf[1]);
208 			if (oticks - ticks)
209 			    seq_addevent(outputbuf, 4);
210 		    }
211 		    if (outputbuf[0] == SEQ_MIDIPUTC) {
212 			if (outputbuf[1] & 0x80)
213 			    cmd = (mid[db = 0] = outputbuf[1]) & 0xf0;
214 			else
215 			    mid[db] = outputbuf[1];
216 			db++;
217 			if (db == cmdlen[cmd >> 4] + 1) {
218 			    if (transpose && (cmd == 0x80 || cmd == 0x90))
219 				mid[1] += transpose;
220 			    fprintf(stderr, "<%8d ", oticks);
221 			    for (i = 0; i < db; i++) {
222 				if (channel >= 1 && channel <= 16)
223 				    mid[0] = cmd | (channel - 1);
224 				fprintf(stderr, "%02x", mid[i]);
225 				SEQ_MIDIOUT(OUT_DEV, mid[i]);
226 			    }
227 			    fprintf(stderr, "\n");
228 			    db = 1;
229 			    SEQ_DUMPBUF();
230 			}
231 		    }
232 		}
233 		gettimeofday(&now, NULL);
234 	    }
235 	    seq_addevent(inputbuf, 4);
236 	}
237 	SEQ_DUMPBUF();
238     }
239     /* should NEVER get to here, if we do, something is really screwed */
240     close(seqfd);
241     close(infile);
242     close(outfile);
243     exit(0);
244 }
245 /* end of file */
246