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