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 /**/ 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 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); patestCallback(const void * inputBuffer,void * outputBuffer,unsigned long framesPerBuffer,const PaStreamCallbackTimeInfo * timeInfo,PaStreamCallbackFlags statusFlags,void * userData)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 */ main(void)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 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 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