1 #include "portmidi.h"
2 #include "porttime.h"
3 #include "stdlib.h"
4 #include "stdio.h"
5 #include "string.h"
6 #include "assert.h"
7 
8 #define INPUT_BUFFER_SIZE 100
9 #define OUTPUT_BUFFER_SIZE 0
10 #define DRIVER_INFO NULL
11 #define TIME_PROC ((int32_t (*)(void *)) Pt_Time)
12 #define TIME_INFO NULL
13 #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
14 
15 #define STRING_MAX 80 /* used for console input */
16 
17 int32_t latency = 0;
18 
19 /* crash the program to test whether midi ports are closed */
20 /**/
doSomethingReallyStupid()21 void doSomethingReallyStupid() {
22 	int * tmp = NULL;
23 	*tmp = 5;
24 }
25 
26 
27 /* exit the program without any explicit cleanup */
28 /**/
doSomethingStupid()29 void doSomethingStupid() {
30 	assert(0);
31 }
32 
33 
34 /* read a number from console */
35 /**/
get_number(char * prompt)36 int get_number(char *prompt)
37 {
38     char line[STRING_MAX];
39     int n = 0, i;
40     printf(prompt);
41     while (n != 1) {
42         n = scanf("%d", &i);
43         fgets(line, STRING_MAX, stdin);
44 
45     }
46     return i;
47 }
48 
49 
50 /*
51  * the somethingStupid parameter can be set to simulate a program crash.
52  * We want PortMidi to close Midi ports automatically in the event of a
53  * crash because Windows does not (and this may cause an OS crash)
54  */
main_test_input(unsigned int somethingStupid)55 void main_test_input(unsigned int somethingStupid) {
56     PmStream * midi;
57     PmError status, length;
58     PmEvent buffer[1];
59     int num = 10;
60     int i = get_number("Type input number: ");
61     /* It is recommended to start timer before Midi; otherwise, PortMidi may
62        start the timer with its (default) parameters
63      */
64     TIME_START;
65 
66     /* open input device */
67     Pm_OpenInput(&midi,
68                  i,
69                  DRIVER_INFO,
70                  INPUT_BUFFER_SIZE,
71                  TIME_PROC,
72                  TIME_INFO);
73 
74     printf("Midi Input opened. Reading %d Midi messages...\n", num);
75     Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
76     /* empty the buffer after setting filter, just in case anything
77        got through */
78     while (Pm_Poll(midi)) {
79         Pm_Read(midi, buffer, 1);
80     }
81     /* now start paying attention to messages */
82     i = 0; /* count messages as they arrive */
83     while (i < num) {
84         status = Pm_Poll(midi);
85         if (status == TRUE) {
86             length = Pm_Read(midi,buffer, 1);
87             if (length > 0) {
88                 printf("Got message %d: time %ld, %2lx %2lx %2lx\n",
89                        i,
90                        (long) buffer[0].timestamp,
91                        (long) Pm_MessageStatus(buffer[0].message),
92                        (long) Pm_MessageData1(buffer[0].message),
93                        (long) Pm_MessageData2(buffer[0].message));
94                 i++;
95             } else {
96                 assert(0);
97             }
98         }
99         /* simulate crash if somethingStupid is 1 or 2 */
100         if ((i > (num/2)) && (somethingStupid == 1)) {
101             doSomethingStupid();
102         } else if ((i > (num/2)) && (somethingStupid == 2)) {
103             doSomethingReallyStupid();
104         }
105     }
106 
107     /* close device (this not explicitly needed in most implementations) */
108     printf("ready to close...");
109 
110     Pm_Close(midi);
111     printf("done closing...");
112 }
113 
114 
115 
main_test_output()116 void main_test_output() {
117     PmStream * midi;
118 	char line[80];
119     int32_t off_time;
120     int chord[] = { 60, 67, 76, 83, 90 };
121     #define chord_size 5
122     PmEvent buffer[chord_size];
123     PmTimestamp timestamp;
124 
125     /* determine which output device to use */
126     int i = get_number("Type output number: ");
127 
128     /* It is recommended to start timer before PortMidi */
129     TIME_START;
130 
131     /* open output device -- since PortMidi avoids opening a timer
132        when latency is zero, we will pass in a NULL timer pointer
133        for that case. If PortMidi tries to access the time_proc,
134        we will crash, so this test will tell us something. */
135     Pm_OpenOutput(&midi,
136                   i,
137                   DRIVER_INFO,
138                   OUTPUT_BUFFER_SIZE,
139                   (latency == 0 ? NULL : TIME_PROC),
140                   (latency == 0 ? NULL : TIME_INFO),
141                   latency);
142     printf("Midi Output opened with %ld ms latency.\n", (long) latency);
143 
144     /* output note on/off w/latency offset; hold until user prompts */
145     printf("ready to send program 1 change... (type RETURN):");
146     fgets(line, STRING_MAX, stdin);
147     /* if we were writing midi for immediate output, we could always use
148        timestamps of zero, but since we may be writing with latency, we
149        will explicitly set the timestamp to "now" by getting the time.
150        The source of timestamps should always correspond to the TIME_PROC
151        and TIME_INFO parameters used in Pm_OpenOutput(). */
152     buffer[0].timestamp = TIME_PROC(TIME_INFO);
153     /* Send a program change to increase the chances we will hear notes */
154     /* Program 0 is usually a piano, but you can change it here: */
155 #define PROGRAM 0
156     buffer[0].message = Pm_Message(0xC0, PROGRAM, 0);
157     Pm_Write(midi, buffer, 1);
158 
159     printf("ready to note-on... (type RETURN):");
160     fgets(line, STRING_MAX, stdin);
161     buffer[0].timestamp = TIME_PROC(TIME_INFO);
162     buffer[0].message = Pm_Message(0x90, 60, 100);
163     Pm_Write(midi, buffer, 1);
164     printf("ready to note-off... (type RETURN):");
165     fgets(line, STRING_MAX, stdin);
166     buffer[0].timestamp = TIME_PROC(TIME_INFO);
167     buffer[0].message = Pm_Message(0x90, 60, 0);
168     Pm_Write(midi, buffer, 1);
169 
170     /* output short note on/off w/latency offset; hold until user prompts */
171     printf("ready to note-on (short form)... (type RETURN):");
172     fgets(line, STRING_MAX, stdin);
173     Pm_WriteShort(midi, TIME_PROC(TIME_INFO),
174                   Pm_Message(0x90, 60, 100));
175     printf("ready to note-off (short form)... (type RETURN):");
176     fgets(line, STRING_MAX, stdin);
177     Pm_WriteShort(midi, TIME_PROC(TIME_INFO),
178                   Pm_Message(0x90, 60, 0));
179 
180     /* output several note on/offs to test timing.
181        Should be 1s between notes */
182     printf("chord will arpeggiate if latency > 0\n");
183     printf("ready to chord-on/chord-off... (type RETURN):");
184     fgets(line, STRING_MAX, stdin);
185     timestamp = TIME_PROC(TIME_INFO);
186     for (i = 0; i < chord_size; i++) {
187         buffer[i].timestamp = timestamp + 1000 * i;
188         buffer[i].message = Pm_Message(0x90, chord[i], 100);
189     }
190     Pm_Write(midi, buffer, chord_size);
191 
192     off_time = timestamp + 1000 + chord_size * 1000;
193     while (TIME_PROC(TIME_INFO) < off_time)
194 		/* busy wait */;
195     for (i = 0; i < chord_size; i++) {
196         buffer[i].timestamp = timestamp + 1000 * i;
197         buffer[i].message = Pm_Message(0x90, chord[i], 0);
198     }
199     Pm_Write(midi, buffer, chord_size);
200 
201     /* close device (this not explicitly needed in most implementations) */
202     printf("ready to close and terminate... (type RETURN):");
203     fgets(line, STRING_MAX, stdin);
204 
205     Pm_Close(midi);
206     Pm_Terminate();
207     printf("done closing and terminating...\n");
208 }
209 
210 
main_test_both()211 void main_test_both()
212 {
213     int i = 0;
214     int in, out;
215     PmStream * midi, * midiOut;
216     PmEvent buffer[1];
217     PmError status, length;
218     int num = 10;
219 
220     in = get_number("Type input number: ");
221     out = get_number("Type output number: ");
222 
223     /* In is recommended to start timer before PortMidi */
224     TIME_START;
225 
226     Pm_OpenOutput(&midiOut,
227                   out,
228                   DRIVER_INFO,
229                   OUTPUT_BUFFER_SIZE,
230                   TIME_PROC,
231                   TIME_INFO,
232                   latency);
233     printf("Midi Output opened with %ld ms latency.\n", (long) latency);
234     /* open input device */
235     Pm_OpenInput(&midi,
236                  in,
237                  DRIVER_INFO,
238                  INPUT_BUFFER_SIZE,
239                  TIME_PROC,
240                  TIME_INFO);
241     printf("Midi Input opened. Reading %d Midi messages...\n",num);
242     Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK);
243     /* empty the buffer after setting filter, just in case anything
244        got through */
245     while (Pm_Poll(midi)) {
246         Pm_Read(midi, buffer, 1);
247     }
248     i = 0;
249     while (i < num) {
250         status = Pm_Poll(midi);
251         if (status == TRUE) {
252             length = Pm_Read(midi,buffer,1);
253             if (length > 0) {
254                 Pm_Write(midiOut, buffer, 1);
255                 printf("Got message %d: time %ld, %2lx %2lx %2lx\n",
256                        i,
257                        (long) buffer[0].timestamp,
258                        (long) Pm_MessageStatus(buffer[0].message),
259                        (long) Pm_MessageData1(buffer[0].message),
260                        (long) Pm_MessageData2(buffer[0].message));
261                 i++;
262             } else {
263                 assert(0);
264             }
265         }
266     }
267 
268     /* close midi devices */
269     Pm_Close(midi);
270     Pm_Close(midiOut);
271     Pm_Terminate();
272 }
273 
274 
275 /* main_test_stream exercises windows winmm API's stream mode */
276 /*    The winmm stream mode is used for latency>0, and sends
277    timestamped messages. The timestamps are relative (delta)
278    times, whereas PortMidi times are absolute. Since peculiar
279    things happen when messages are not always sent in advance,
280    this function allows us to exercise the system and test it.
281  */
main_test_stream()282 void main_test_stream() {
283     PmStream * midi;
284 	char line[80];
285     PmEvent buffer[16];
286 
287 	/* determine which output device to use */
288     int i = get_number("Type output number: ");
289 
290 	latency = 500; /* ignore LATENCY for this test and
291 				      fix the latency at 500ms */
292 
293     /* It is recommended to start timer before PortMidi */
294     TIME_START;
295 
296 	/* open output device */
297     Pm_OpenOutput(&midi,
298                   i,
299                   DRIVER_INFO,
300                   OUTPUT_BUFFER_SIZE,
301                   TIME_PROC,
302                   TIME_INFO,
303                   latency);
304     printf("Midi Output opened with %ld ms latency.\n", (long) latency);
305 
306     /* output note on/off w/latency offset; hold until user prompts */
307     printf("ready to send output... (type RETURN):");
308     fgets(line, STRING_MAX, stdin);
309 
310     /* if we were writing midi for immediate output, we could always use
311        timestamps of zero, but since we may be writing with latency, we
312        will explicitly set the timestamp to "now" by getting the time.
313        The source of timestamps should always correspond to the TIME_PROC
314        and TIME_INFO parameters used in Pm_OpenOutput(). */
315     buffer[0].timestamp = TIME_PROC(TIME_INFO);
316     buffer[0].message = Pm_Message(0xC0, 0, 0);
317 	buffer[1].timestamp = buffer[0].timestamp;
318 	buffer[1].message = Pm_Message(0x90, 60, 100);
319 	buffer[2].timestamp = buffer[0].timestamp + 1000;
320 	buffer[2].message = Pm_Message(0x90, 62, 100);
321 	buffer[3].timestamp = buffer[0].timestamp + 2000;
322 	buffer[3].message = Pm_Message(0x90, 64, 100);
323 	buffer[4].timestamp = buffer[0].timestamp + 3000;
324 	buffer[4].message = Pm_Message(0x90, 66, 100);
325 	buffer[5].timestamp = buffer[0].timestamp + 4000;
326 	buffer[5].message = Pm_Message(0x90, 60, 0);
327 	buffer[6].timestamp = buffer[0].timestamp + 4000;
328 	buffer[6].message = Pm_Message(0x90, 62, 0);
329 	buffer[7].timestamp = buffer[0].timestamp + 4000;
330 	buffer[7].message = Pm_Message(0x90, 64, 0);
331 	buffer[8].timestamp = buffer[0].timestamp + 4000;
332 	buffer[8].message = Pm_Message(0x90, 66, 0);
333 
334     Pm_Write(midi, buffer, 9);
335 #ifdef SEND8
336 	/* Now, we're ready for the real test.
337 	   Play 4 notes at now, now+500, now+1000, and now+1500
338 	   Then wait until now+2000.
339 	   Play 4 more notes as before.
340 	   We should hear 8 evenly spaced notes. */
341 	now = TIME_PROC(TIME_INFO);
342 	for (i = 0; i < 4; i++) {
343 		buffer[i * 2].timestamp = now + (i * 500);
344 		buffer[i * 2].message = Pm_Message(0x90, 60, 100);
345 		buffer[i * 2 + 1].timestamp = now + 250 + (i * 500);
346 		buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0);
347 	}
348     Pm_Write(midi, buffer, 8);
349 
350     while (Pt_Time() < now + 2500)
351 		/* busy wait */;
352 	/* now we are 500 ms behind schedule, but since the latency
353 	   is 500, the delay should not be audible */
354 	now += 2000;
355 	for (i = 0; i < 4; i++) {
356 		buffer[i * 2].timestamp = now + (i * 500);
357 		buffer[i * 2].message = Pm_Message(0x90, 60, 100);
358 		buffer[i * 2 + 1].timestamp = now + 250 + (i * 500);
359 		buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0);
360 	}
361     Pm_Write(midi, buffer, 8);
362 #endif
363     /* close device (this not explicitly needed in most implementations) */
364     printf("ready to close and terminate... (type RETURN):");
365     fgets(line, STRING_MAX, stdin);
366 
367     Pm_Close(midi);
368     Pm_Terminate();
369     printf("done closing and terminating...\n");
370 }
371 
372 
show_usage()373 void show_usage()
374 {
375     printf("Usage: test [-h] [-l latency-in-ms]\n");
376     exit(0);
377 }
378 
main(int argc,char * argv[])379 int main(int argc, char *argv[])
380 {
381     int default_in;
382     int default_out;
383     int i = 0, n = 0;
384     char line[STRING_MAX];
385     int test_input = 0, test_output = 0, test_both = 0, somethingStupid = 0;
386     int stream_test = 0;
387     int latency_valid = FALSE;
388 
389     if (sizeof(void *) == 8)
390         printf("Apparently this is a 64-bit machine.\n");
391     else if (sizeof(void *) == 4)
392         printf ("Apparently this is a 32-bit machine.\n");
393 
394     for (i = 1; i < argc; i++) {
395         if (strcmp(argv[i], "-h") == 0) {
396             show_usage();
397         } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
398             i = i + 1;
399             latency = atoi(argv[i]);
400             printf("Latency will be %ld\n", (long) latency);
401             latency_valid = TRUE;
402         } else {
403             show_usage();
404         }
405     }
406 
407     while (!latency_valid) {
408         int lat; // declared int to match "%d"
409         printf("Latency in ms: ");
410         if (scanf("%d", &lat) == 1) {
411             latency = (int32_t) lat; // coerce from "%d" to known size
412 	    latency_valid = TRUE;
413         }
414     }
415 
416     /* determine what type of test to run */
417     printf("begin portMidi test...\n");
418     printf("%s%s%s%s%s",
419            "enter your choice...\n    1: test input\n",
420            "    2: test input (fail w/assert)\n",
421            "    3: test input (fail w/NULL assign)\n",
422            "    4: test output\n    5: test both\n",
423            "    6: stream test\n");
424     while (n != 1) {
425         n = scanf("%d", &i);
426         fgets(line, STRING_MAX, stdin);
427         switch(i) {
428         case 1:
429             test_input = 1;
430             break;
431         case 2:
432             test_input = 1;
433             somethingStupid = 1;
434             break;
435         case 3:
436             test_input = 1;
437             somethingStupid = 2;
438             break;
439         case 4:
440             test_output = 1;
441             break;
442         case 5:
443             test_both = 1;
444             break;
445 		case 6:
446 			stream_test = 1;
447 			break;
448         default:
449             printf("got %d (invalid input)\n", n);
450             break;
451         }
452     }
453 
454     /* list device information */
455     default_in = Pm_GetDefaultInputDeviceID();
456     default_out = Pm_GetDefaultOutputDeviceID();
457     for (i = 0; i < Pm_CountDevices(); i++) {
458         char *deflt;
459         const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
460         if (((test_input  | test_both) & info->input) |
461             ((test_output | test_both | stream_test) & info->output)) {
462             printf("%d: %s, %s", i, info->interf, info->name);
463             if (info->input) {
464                 deflt = (i == default_in ? "default " : "");
465                 printf(" (%sinput)", deflt);
466             }
467             if (info->output) {
468                 deflt = (i == default_out ? "default " : "");
469                 printf(" (%soutput)", deflt);
470             }
471             printf("\n");
472         }
473     }
474 
475     /* run test */
476     if (stream_test) {
477         main_test_stream();
478     } else if (test_input) {
479         main_test_input(somethingStupid);
480     } else if (test_output) {
481         main_test_output();
482     } else if (test_both) {
483         main_test_both();
484     }
485 
486     printf("finished portMidi test...type ENTER to quit...");
487     fgets(line, STRING_MAX, stdin);
488     return 0;
489 }
490