aboutsummaryrefslogtreecommitdiff
path: root/portmidi/pm_test/fast.c
diff options
context:
space:
mode:
Diffstat (limited to 'portmidi/pm_test/fast.c')
-rw-r--r--portmidi/pm_test/fast.c290
1 files changed, 290 insertions, 0 deletions
diff --git a/portmidi/pm_test/fast.c b/portmidi/pm_test/fast.c
new file mode 100644
index 0000000..102697e
--- /dev/null
+++ b/portmidi/pm_test/fast.c
@@ -0,0 +1,290 @@
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 * Modified 9 Aug 2017 with -m, -p to test when timestamps are
11 * wrapping from negative to positive or positive to negative.
12 *
13 * Roger B. Dannenberg, Aug 2017
14 */
15
16#include "portmidi.h"
17#include "porttime.h"
18#include "stdlib.h"
19#include "stdio.h"
20#include "string.h"
21#include "assert.h"
22
23#define DEVICE_INFO NULL
24#define DRIVER_INFO NULL
25#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
26
27#define STRING_MAX 80 /* used for console input */
28// need to get declaration for Sleep()
29#ifdef WIN32
30#include "windows.h"
31#else
32#include <unistd.h>
33#define Sleep(n) usleep(n * 1000)
34#endif
35
36
37int32_t latency = 0;
38int32_t msgrate = 0;
39int deviceno = -9999;
40int duration = 0;
41int expired_timestamps = FALSE;
42int use_timeoffset = 0;
43
44/* read a number from console */
45/**/
46int get_number(const char *prompt)
47{
48 int n = 0, i;
49 fputs(prompt, stdout);
50 while (n != 1) {
51 n = scanf("%d", &i);
52 while (getchar() != '\n') ;
53 }
54 return i;
55}
56
57
58/* get_time -- the time reference. Normally, this will be the default
59 * time, Pt_Time(), but if you use the -p or -m option, the time
60 * reference will start at an offset of -10s for -m, or
61 * maximum_time - 10s for -p, so that we can observe what happens
62 * with negative time or when time changes sign or wraps (by
63 * generating output for more than 10s).
64 */
65PmTimestamp get_time(void *info)
66{
67 PmTimestamp now = (PmTimestamp) (Pt_Time() + use_timeoffset);
68 return now;
69}
70
71
72void fast_test()
73{
74 PmStream *midi;
75 char line[STRING_MAX];
76 int pause = FALSE; /* pause if this is a virtual output port */
77 PmError err = pmNoError;
78 /* output buffer size should be a little more than
79 msgrate * latency / 1000. PortMidi will guarantee
80 a minimum of latency / 2 */
81 int buffer_size = msgrate * latency / 900;
82 PmTimestamp start, now;
83 int msgcnt = 0;
84 int polling_count = 0;
85 int pitch = 60;
86 int printtime = 1000;
87
88 /* It is recommended to start timer before PortMidi */
89 TIME_START;
90
91 /* open output device */
92 if (deviceno == Pm_CountDevices()) {
93 deviceno = Pm_CreateVirtualOutput("fast", NULL, DEVICE_INFO);
94 if (deviceno >= 0) {
95 err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size,
96 get_time, NULL, latency);
97 pause = TRUE;
98 }
99 } else if (err >= pmNoError) {
100 err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size,
101 get_time, NULL, latency);
102 }
103 if (err == pmHostError) {
104 Pm_GetHostErrorText(line, STRING_MAX);
105 printf("PortMidi found host error...\n %s\n", line);
106 goto done;
107 } else if (err < 0) {
108 printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
109 goto done;
110 }
111 printf("Midi Output opened with %ld ms latency.\n", (long) latency);
112 if (pause) {
113 printf("Pausing so you can connect a receiver to the newly created\n"
114 " \"fast\" port. Type ENTER to proceed: ");
115 while (getchar() != '\n') ;
116 }
117 /* wait a sec after printing previous line */
118 start = get_time(NULL) + 1000;
119 while (start > get_time(NULL)) {
120 Sleep(10);
121 }
122 printf("sending output...\n");
123 fflush(stdout); /* make sure message goes to console */
124
125 /* every 10ms send on/off pairs at timestamps set to current time */
126 now = get_time(NULL);
127 /* if expired_timestamps, we want to send timestamps that have
128 * expired. They should be sent immediately, but there's a suggestion
129 * that negative delay might cause problems in the ALSA implementation
130 * so this is something we can test using the -n flag.
131 */
132 if (expired_timestamps) {
133 now = now - 2 * latency;
134 }
135
136 while (((PmTimestamp) (now - start)) < duration * 1000 || pitch != 60) {
137 /* how many messages do we send? Total should be
138 * (elapsed * rate) / 1000
139 */
140 int send_total = (((PmTimestamp) ((now - start))) * msgrate) / 1000;
141 /* always send until pitch would be 60 so if we run again, the
142 next pitch (60) will be expected */
143 if (msgcnt < send_total) {
144 if ((msgcnt & 1) == 0) {
145 Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 100));
146 } else {
147 Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 0));
148 /* play 60, 61, 62, ... 71, then wrap back to 60, 61, ... */
149 pitch = (pitch - 59) % 12 + 60;
150 }
151 msgcnt += 1;
152 if (((PmTimestamp) (now - start)) >= printtime) {
153 printf("%d at %dms, polling count %d\n", msgcnt, now - start,
154 polling_count);
155 fflush(stdout); /* make sure message goes to console */
156 printtime += 1000; /* next msg in 1s */
157 }
158 }
159 now = get_time(NULL);
160 polling_count++;
161 }
162 /* close device (this not explicitly needed in most implementations) */
163 printf("ready to close and terminate... (type RETURN):");
164 while (getchar() != '\n') ;
165
166 Pm_Close(midi);
167 done:
168 Pm_Terminate();
169 printf("done closing and terminating...\n");
170}
171
172
173void show_usage()
174{
175 printf("Usage: fast [-h] [-l latency] [-r rate] [-d device] [-s dur] "
176 "[-n] [-p] [-m]\n"
177 ", where latency is in ms,\n"
178 " rate is messages per second,\n"
179 " device is the PortMidi device number,\n"
180 " dur is the length of the test in seconds,\n"
181 " -n means send timestamps in the past,\n"
182 " -p means use a large positive time offset,\n"
183 " -m means use a large negative time offset, and\n"
184 " -h means help.\n");
185}
186
187int main(int argc, char *argv[])
188{
189 int default_in;
190 int default_out;
191 char *deflt;
192 int i = 0;
193 int latency_valid = FALSE;
194 int rate_valid = FALSE;
195 int device_valid = FALSE;
196 int dur_valid = FALSE;
197
198 if (sizeof(void *) == 8)
199 printf("Apparently this is a 64-bit machine.\n");
200 else if (sizeof(void *) == 4)
201 printf ("Apparently this is a 32-bit machine.\n");
202
203 if (argc <= 1) {
204 show_usage();
205 } else {
206 for (i = 1; i < argc; i++) {
207 if (strcmp(argv[i], "-h") == 0) {
208 show_usage();
209 } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
210 i = i + 1;
211 latency = atoi(argv[i]);
212 printf("Latency will be %ld\n", (long) latency);
213 latency_valid = TRUE;
214 } else if (strcmp(argv[i], "-r") == 0) {
215 i = i + 1;
216 msgrate = atoi(argv[i]);
217 printf("Rate will be %d messages/second\n", msgrate);
218 rate_valid = TRUE;
219 } else if (strcmp(argv[i], "-d") == 0) {
220 i = i + 1;
221 deviceno = atoi(argv[i]);
222 printf("Device will be %d\n", deviceno);
223 } else if (strcmp(argv[i], "-s") == 0) {
224 i = i + 1;
225 duration = atoi(argv[i]);
226 printf("Duration will be %d seconds\n", duration);
227 dur_valid = TRUE;
228 } else if (strcmp(argv[i], "-n") == 0) {
229 printf("Sending expired timestamps (-n)\n");
230 expired_timestamps = TRUE;
231 } else if (strcmp(argv[i], "-p") == 0) {
232 printf("Time offset set to 2147473648 (-p)\n");
233 use_timeoffset = 2147473648;
234 } else if (strcmp(argv[i], "-m") == 0) {
235 printf("Time offset set to -10000 (-m)\n");
236 use_timeoffset = -10000;
237 } else {
238 show_usage();
239 }
240 }
241 }
242
243 if (!latency_valid) {
244 // coerce to known size
245 latency = (int32_t) get_number("Latency in ms: ");
246 }
247
248 if (!rate_valid) {
249 // coerce from "%d" to known size
250 msgrate = (int32_t) get_number("Rate in messages per second: ");
251 }
252
253 if (!dur_valid) {
254 duration = get_number("Duration in seconds: ");
255 }
256
257 /* list device information */
258 default_in = Pm_GetDefaultInputDeviceID();
259 default_out = Pm_GetDefaultOutputDeviceID();
260 for (i = 0; i < Pm_CountDevices(); i++) {
261 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
262 if (info->output) {
263 printf("%d: %s, %s", i, info->interf, info->name);
264 if (i == deviceno) {
265 device_valid = TRUE;
266 deflt = "selected ";
267 } else if (i == default_out) {
268 deflt = "default ";
269 } else {
270 deflt = "";
271 }
272 printf(" (%soutput)\n", deflt);
273 }
274 }
275 printf("%d: Create virtual port named \"fast\"", i);
276 if (i == deviceno) {
277 device_valid = TRUE;
278 deflt = "selected ";
279 } else {
280 deflt = "";
281 }
282 printf(" (%soutput)\n", deflt);
283
284 if (!device_valid) {
285 deviceno = get_number("Output device number: ");
286 }
287
288 fast_test();
289 return 0;
290}