aboutsummaryrefslogtreecommitdiff
path: root/portmidi/pm_test/latency.c
diff options
context:
space:
mode:
Diffstat (limited to 'portmidi/pm_test/latency.c')
-rwxr-xr-xportmidi/pm_test/latency.c287
1 files changed, 287 insertions, 0 deletions
diff --git a/portmidi/pm_test/latency.c b/portmidi/pm_test/latency.c
new file mode 100755
index 0000000..06ea80d
--- /dev/null
+++ b/portmidi/pm_test/latency.c
@@ -0,0 +1,287 @@
1/* latency.c -- measure latency of OS */
2
3#include "porttime.h"
4#include "portmidi.h"
5#include "stdlib.h"
6#include "stdio.h"
7#include "string.h"
8#include "assert.h"
9
10/* Latency is defined here to mean the time starting when a
11 process becomes ready to run, and ending when the process
12 actually runs. Latency is due to contention for the
13 processor, usually due to other processes, OS activity
14 including device drivers handling interrupts, and
15 waiting for the scheduler to suspend the currently running
16 process and activate the one that is waiting.
17
18 Latency can affect PortMidi applications: if a process fails
19 to wake up promptly, MIDI input may sit in the input buffer
20 waiting to be handled, and MIDI output may not be generated
21 with accurate timing. Using the latency parameter when
22 opening a MIDI output port allows the caller to defer timing
23 to PortMidi, which in most implementations will pass the
24 data on to the OS. By passing timestamps and data to the
25 OS kernel, device driver, or even hardware, there are fewer
26 sources of latency that can affect the ultimate timing of
27 the data. On the other hand, the application must generate
28 and deliver the data ahead of the timestamp. The amount by
29 which data is computed early must be at least as large as
30 the worst-case latency to avoid timing problems.
31
32 Latency is even more important in audio applications. If an
33 application lets an audio output buffer underflow, an audible
34 pop or click is produced. Audio input buffers can overflow,
35 causing data to be lost. In general the audio buffers must
36 be large enough to buffer the worst-case latency that the
37 application will encounter.
38
39 This program measures latency by recording the difference
40 between the scheduled callback time and the current real time.
41 We do not really know the scheduled callback time, so we will
42 record the differences between the real time of each callback
43 and the real time of the previous callback. Differences that
44 are larger than the scheduled difference are recorded. Smaller
45 differences indicate the system is recovering from an earlier
46 latency, so these are ignored.
47 Since printing by the callback process can cause all sorts of
48 delays, this program records latency observations in a
49 histogram. When the program is stopped, the histogram is
50 printed to the console.
51
52 Optionally the system can be tested under a load of MIDI input,
53 MIDI output, or both. If MIDI input is selected, the callback
54 thread will read any waiting MIDI events each iteration. You
55 must generate events on this interface for the test to actually
56 put any appreciable load on PortMidi. If MIDI output is
57 selected, alternating note on and note off events are sent each
58 X iterations, where you specify X. For example, with a timer
59 callback period of 2ms and X=1, a MIDI event is sent every 2ms.
60
61
62 INTERPRETING RESULTS: Time is quantized to 1ms, so there is
63 some uncertainty due to rounding. A microsecond latency that
64 spans the time when the clock is incremented will be reported
65 as a latency of 1. On the other hand, a latency of almost
66 1ms that falls between two clock ticks will be reported as
67 zero. In general, if the highest nonzero bin is numbered N,
68 then the maximum latency is N+1.
69
70CHANGE LOG
71
7218-Jul-03 Mark Nelson -- Added code to generate MIDI or receive
73 MIDI during test, and made period user-settable.
74 */
75
76#define HIST_LEN 21 /* how many 1ms bins in the histogram */
77
78#define STRING_MAX 80 /* used for console input */
79
80#define INPUT_BUFFER_SIZE 100
81#define OUTPUT_BUFFER_SIZE 0
82
83#ifndef max
84#define max(a, b) ((a) > (b) ? (a) : (b))
85#endif
86#ifndef min
87#define min(a, b) ((a) <= (b) ? (a) : (b))
88#endif
89
90int get_number(const char *prompt);
91
92PtTimestamp previous_callback_time = 0;
93
94int period; /* milliseconds per callback */
95
96int histogram[HIST_LEN];
97int max_latency = 0; /* worst latency observed */
98int out_of_range = 0; /* how many points outside of HIST_LEN? */
99
100int test_in, test_out; /* test MIDI in and/or out? */
101int output_period; /* output MIDI every __ iterations if test_out true */
102int iteration = 0;
103PmStream *in, *out;
104int note_on = 0; /* is the note currently on? */
105
106/* callback function for PortTime -- computes histogram */
107void pt_callback(PtTimestamp timestamp, void *userData)
108{
109 PtTimestamp difference = timestamp - previous_callback_time - period;
110 previous_callback_time = timestamp;
111
112 /* allow 5 seconds for the system to settle down */
113 if (timestamp < 5000) return;
114
115 iteration++;
116 /* send a note on/off if user requested it */
117 if (test_out && (iteration % output_period == 0)) {
118 PmEvent buffer[1];
119 buffer[0].timestamp = Pt_Time();
120 if (note_on) {
121 /* note off */
122 buffer[0].message = Pm_Message(0x90, 60, 0);
123 note_on = 0;
124 } else {
125 /* note on */
126 buffer[0].message = Pm_Message(0x90, 60, 100);
127 note_on = 1;
128 }
129 Pm_Write(out, buffer, 1);
130 iteration = 0;
131 }
132
133 /* read all waiting events (if user requested) */
134 if (test_in) {
135 PmError status;
136 PmEvent buffer[1];
137 do {
138 status = Pm_Poll(in);
139 if (status == TRUE) {
140 Pm_Read(in,buffer,1);
141 }
142 } while (status == TRUE);
143 }
144
145 if (difference < 0) return; /* ignore when system is "catching up" */
146
147 /* update the histogram */
148 if (difference < HIST_LEN) {
149 histogram[difference]++;
150 } else {
151 out_of_range++;
152 }
153
154 if (max_latency < difference) max_latency = difference;
155}
156
157
158int main()
159{
160 int i;
161 int len;
162 int choice;
163 PtTimestamp stop;
164 printf("Latency histogram.\n");
165 period = 0;
166 while (period < 1) {
167 period = get_number("Choose timer period (in ms, >= 1): ");
168 }
169 printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n",
170 "1. No MIDI traffic",
171 "2. MIDI input",
172 "3. MIDI output",
173 "4. MIDI input and output");
174 choice = get_number("? ");
175 switch (choice) {
176 case 1: test_in = 0; test_out = 0; break;
177 case 2: test_in = 1; test_out = 0; break;
178 case 3: test_in = 0; test_out = 1; break;
179 case 4: test_in = 1; test_out = 1; break;
180 default: assert(0);
181 }
182 if (test_in || test_out) {
183 /* list device information */
184 for (i = 0; i < Pm_CountDevices(); i++) {
185 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
186 if ((test_in && info->input) ||
187 (test_out && info->output)) {
188 printf("%d: %s, %s", i, info->interf, info->name);
189 if (info->input) printf(" (input)");
190 if (info->output) printf(" (output)");
191 printf("\n");
192 }
193 }
194 /* open stream(s) */
195 if (test_in) {
196 int i = get_number("MIDI input device number: ");
197 Pm_OpenInput(&in,
198 i,
199 NULL,
200 INPUT_BUFFER_SIZE,
201 (PmTimestamp (*)(void *)) Pt_Time,
202 NULL);
203 /* turn on filtering; otherwise, input might overflow in the
204 5-second period before timer callback starts reading midi */
205 Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK);
206 }
207 if (test_out) {
208 int i = get_number("MIDI output device number: ");
209 PmEvent buffer[1];
210 Pm_OpenOutput(&out,
211 i,
212 NULL,
213 OUTPUT_BUFFER_SIZE,
214 (PmTimestamp (*)(void *)) Pt_Time,
215 NULL,
216 0); /* no latency scheduling */
217
218 /* send a program change to force a status byte -- this fixes
219 a problem with a buggy linux MidiSport driver, and shouldn't
220 hurt anything else
221 */
222 buffer[0].timestamp = 0;
223 buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */
224 Pm_Write(out, buffer, 1);
225
226 output_period = get_number(
227 "MIDI out should be sent every __ callback iterations: ");
228
229 assert(output_period >= 1);
230 }
231 }
232
233 printf("Latency measurements will start in 5 seconds. "
234 "Type return to stop: ");
235 Pt_Start(period, &pt_callback, 0);
236 while (getchar() != '\n') ;
237 stop = Pt_Time();
238 Pt_Stop();
239
240 /* courteously turn off the last note, if necessary */
241 if (note_on) {
242 PmEvent buffer[1];
243 buffer[0].timestamp = Pt_Time();
244 buffer[0].message = Pm_Message(0x90, 60, 0);
245 Pm_Write(out, buffer, 1);
246 }
247
248 /* print the histogram */
249 printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001);
250 printf("Latency(ms) Number of occurrences\n");
251 /* avoid printing beyond last non-zero histogram entry */
252 len = min(HIST_LEN, max_latency + 1);
253 for (i = 0; i < len; i++) {
254 printf("%2d %10d\n", i, histogram[i]);
255 }
256 printf("Number of points greater than %dms: %d\n",
257 HIST_LEN - 1, out_of_range);
258 printf("Maximum latency: %d milliseconds\n", max_latency);
259 printf("\nNote that due to rounding, actual latency can be 1ms higher\n");
260 printf("than the numbers reported here.\n");
261 printf("Type return to exit...");
262 while (getchar() != '\n') ;
263
264 if(choice == 2)
265 Pm_Close(in);
266 else if(choice == 3)
267 Pm_Close(out);
268 else if(choice == 4)
269 {
270 Pm_Close(in);
271 Pm_Close(out);
272 }
273 return 0;
274}
275
276
277/* read a number from console */
278int get_number(const char *prompt)
279{
280 int n = 0, i;
281 fputs(prompt, stdout);
282 while (n != 1) {
283 n = scanf("%d", &i);
284 while (getchar() != '\n') ;
285 }
286 return i;
287}