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