1 /* fast.c -- send many MIDI messages very fast.
2  *
3  * This is a stress test created to explore reports of
4  * pm_write() call blocking (forever) on Linux when
5  * sending very dense MIDI sequences.
6  *
7  * Modified 8 Aug 2017 with -n to send expired timestamps
8  * to test a theory about why Linux ALSA hangs in Audacity.
9  *
10  * Roger B. Dannenberg, Aug 2017
11  */
12 
13 #include "portmidi.h"
14 #include "porttime.h"
15 #include "stdlib.h"
16 #include "stdio.h"
17 #include "string.h"
18 #include "assert.h"
19 
20 #define OUTPUT_BUFFER_SIZE 0
21 #define DRIVER_INFO NULL
22 #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
23 
24 #define STRING_MAX 80 /* used for console input */
25 // need to get declaration for Sleep()
26 #ifdef WIN32
27 #include "windows.h"
28 #else
29 #include <unistd.h>
30 #define Sleep(n) usleep(n * 1000)
31 #endif
32 
33 
34 int32_t latency = 0;
35 int32_t msgrate = 0;
36 int deviceno = -9999;
37 int duration = 0;
38 int expired_timestamps = FALSE;
39 
40 /* read a number from console */
41 /**/
get_number(char * prompt)42 int get_number(char *prompt)
43 {
44     char line[STRING_MAX];
45     int n = 0, i;
46     printf("%s", prompt);
47     while (n != 1) {
48         n = scanf("%d", &i);
49         fgets(line, STRING_MAX, stdin);
50     }
51     return i;
52 }
53 
54 
fast_test()55 void fast_test()
56 {
57     PmStream * midi;
58 	char line[80];
59     PmEvent buffer[16];
60 
61     /* It is recommended to start timer before PortMidi */
62     TIME_START;
63 
64 	/* open output device */
65     Pm_OpenOutput(&midi,
66                   deviceno,
67                   DRIVER_INFO,
68                   OUTPUT_BUFFER_SIZE,
69                   (int32_t (*)(void *)) Pt_Time,
70                   NULL,
71                   latency);
72     printf("Midi Output opened with %ld ms latency.\n", (long) latency);
73 
74     /* wait a sec after printing previous line */
75     PmTimestamp start = Pt_Time() + 1000;
76     while (start > Pt_Time()) {
77         Sleep(10);
78     }
79     printf("sending output...\n");
80     fflush(stdout); /* make sure message goes to console */
81 
82     /* every 10ms send on/off pairs at timestamps set to current time */
83     PmTimestamp now = Pt_Time();
84     int msgcnt = 0;
85     int pitch = 60;
86     int printtime = 1000;
87     /* if expired_timestamps, we want to send timestamps that have
88      * expired. They should be sent immediately, but there's a suggestion
89      * that negative delay might cause problems in the ALSA implementation
90      * so this is something we can test using the -n flag.
91      */
92     if (expired_timestamps) {
93         now = now - 2 * latency;
94     }
95     while (now - start < duration *  1000) {
96         /* how many messages do we send? Total should be
97          *     (elapsed * rate) / 1000
98          */
99         int send_total = ((now - start) * msgrate) / 1000;
100         while (msgcnt < send_total) {
101             Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 100));
102             Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 0));
103             msgcnt += 2;
104             /* play 60, 61, 62, ... 71, then wrap back to 60, 61, ... */
105             pitch = (pitch - 59) % 12 + 60;
106             if (now - start >= printtime) {
107                 printf("%d at %dms\n", msgcnt, now - start);
108                 fflush(stdout); /* make sure message goes to console */
109                 printtime += 1000; /* next msg in 1s */
110             }
111         }
112         now = Pt_Time();
113     }
114     /* close device (this not explicitly needed in most implementations) */
115     printf("ready to close and terminate... (type RETURN):");
116     fgets(line, STRING_MAX, stdin);
117 
118     Pm_Close(midi);
119     Pm_Terminate();
120     printf("done closing and terminating...\n");
121 }
122 
123 
show_usage()124 void show_usage()
125 {
126     printf("Usage: test [-h] %s\nwhere %s\n%s\n%s\n%s\n",
127            "[-l latency] [-r rate] [-d device] [-s dur] [-n]",
128            "latency is in ms, rate is messages per second,",
129            "device is the PortMidi device number,",
130            "dur is the length of the test in seconds, and",
131            "-n means send timestamps in the past, -h means help.");
132 }
133 
main(int argc,char * argv[])134 int main(int argc, char *argv[])
135 {
136     int default_in;
137     int default_out;
138     int i = 0, n = 0;
139     char line[STRING_MAX];
140     int test_input = 0, test_output = 0, test_both = 0, somethingStupid = 0;
141     int stream_test = 0;
142     int latency_valid = FALSE;
143     int rate_valid = FALSE;
144     int device_valid = FALSE;
145     int dur_valid = FALSE;
146 
147     if (sizeof(void *) == 8)
148         printf("Apparently this is a 64-bit machine.\n");
149     else if (sizeof(void *) == 4)
150         printf ("Apparently this is a 32-bit machine.\n");
151 
152     if (argc <= 1) {
153         show_usage();
154     } else {
155         for (i = 1; i < argc; i++) {
156             if (strcmp(argv[i], "-h") == 0) {
157                 show_usage();
158             } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
159                 i = i + 1;
160                 latency = atoi(argv[i]);
161                 printf("Latency will be %ld\n", (long) latency);
162                 latency_valid = TRUE;
163             } else if (strcmp(argv[i], "-r") == 0) {
164                 i = i + 1;
165                 msgrate = atoi(argv[i]);
166                 printf("Rate will be %d messages/second\n", msgrate);
167                 rate_valid = TRUE;
168             } else if (strcmp(argv[i], "-s") == 0) {
169                 i = i + 1;
170                 duration = atoi(argv[i]);
171                 printf("Duration will be %d seconds\n", msgrate);
172                 dur_valid = TRUE;
173             } else if (strcmp(argv[i], "-n") == 0) {
174                 printf("Sending expired timestamps (-n)\n");
175                 expired_timestamps = TRUE;
176             } else {
177                 show_usage();
178             }
179         }
180     }
181 
182     if (!latency_valid) {
183         // coerce to known size
184         latency = (int32_t) get_number("Latency in ms: ");
185     }
186 
187     if (!rate_valid) {
188         // coerce from "%d" to known size
189         msgrate = (int32_t) get_number("Rate in messages per second: ");
190     }
191 
192     if (!dur_valid) {
193         duration = get_number("Duration in seconds: ");
194     }
195 
196     /* list device information */
197     default_in = Pm_GetDefaultInputDeviceID();
198     default_out = Pm_GetDefaultOutputDeviceID();
199     for (i = 0; i < Pm_CountDevices(); i++) {
200         char *deflt;
201         const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
202         if (info->output) {
203             printf("%d: %s, %s", i, info->interf, info->name);
204             deflt = (i == deviceno ? "selected " :
205                       (i == default_out ? "default " : ""));
206             printf(" (%soutput)", deflt);
207             printf("\n");
208         }
209     }
210 
211     if (!device_valid) {
212         deviceno = get_number("Output device number: ");
213     }
214 
215     fast_test();
216     return 0;
217 }
218