From 90d5b8b4a064420d74678654e94ea4755b377f21 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 15 Dec 2005 00:57:02 +0000 Subject: checking in missing files on behalf of Miller (cleared it with him first). The files are from portmidi17nov04.zip svn path=/trunk/; revision=4216 --- pd/portmidi/pm_test/latency.c | 278 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 pd/portmidi/pm_test/latency.c (limited to 'pd/portmidi/pm_test/latency.c') diff --git a/pd/portmidi/pm_test/latency.c b/pd/portmidi/pm_test/latency.c new file mode 100644 index 00000000..87b1965b --- /dev/null +++ b/pd/portmidi/pm_test/latency.c @@ -0,0 +1,278 @@ +/* latency.c -- measure latency of OS */ + +#include "porttime.h" +#include "portmidi.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +/* Latency is defined here to mean the time starting when a + process becomes ready to run, and ending when the process + actually runs. Latency is due to contention for the + processor, usually due to other processes, OS activity + including device drivers handling interrupts, and + waiting for the scheduler to suspend the currently running + process and activate the one that is waiting. + + Latency can affect PortMidi applications: if a process fails + to wake up promptly, MIDI input may sit in the input buffer + waiting to be handled, and MIDI output may not be generated + with accurate timing. Using the latency parameter when + opening a MIDI output port allows the caller to defer timing + to PortMidi, which in most implementations will pass the + data on to the OS. By passing timestamps and data to the + OS kernel, device driver, or even hardware, there are fewer + sources of latency that can affect the ultimate timing of + the data. On the other hand, the application must generate + and deliver the data ahead of the timestamp. The amount by + which data is computed early must be at least as large as + the worst-case latency to avoid timing problems. + + Latency is even more important in audio applications. If an + application lets an audio output buffer underflow, an audible + pop or click is produced. Audio input buffers can overflow, + causing data to be lost. In general the audio buffers must + be large enough to buffer the worst-case latency that the + application will encounter. + + This program measures latency by recording the difference + between the scheduled callback time and the current real time. + We do not really know the scheduled callback time, so we will + record the differences between the real time of each callback + and the real time of the previous callback. Differences that + are larger than the scheduled difference are recorded. Smaller + differences indicate the system is recovering from an earlier + latency, so these are ignored. + Since printing by the callback process can cause all sorts of + delays, this program records latency observations in a + histogram. When the program is stopped, the histogram is + printed to the console. + + Optionally the system can be tested under a load of MIDI input, + MIDI output, or both. If MIDI input is selected, the callback + thread will read any waiting MIDI events each iteration. You + must generate events on this interface for the test to actually + put any appreciable load on PortMidi. If MIDI output is + selected, alternating note on and note off events are sent each + X iterations, where you specify X. For example, with a timer + callback period of 2ms and X=1, a MIDI event is sent every 2ms. + + + INTERPRETING RESULTS: Time is quantized to 1ms, so there is + some uncertainty due to rounding. A microsecond latency that + spans the time when the clock is incremented will be reported + as a latency of 1. On the other hand, a latency of almost + 1ms that falls between two clock ticks will be reported as + zero. In general, if the highest nonzero bin is numbered N, + then the maximum latency is N+1. + +CHANGE LOG + +18-Jul-03 Mark Nelson -- Added code to generate MIDI or receive + MIDI during test, and made period user-settable. + */ + +#define HIST_LEN 21 /* how many 1ms bins in the histogram */ + +#define STRING_MAX 80 /* used for console input */ + +#define INPUT_BUFFER_SIZE 100 +#define OUTPUT_BUFFER_SIZE 0 + +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef min +#define min(a, b) ((a) <= (b) ? (a) : (b)) +#endif + +int get_number(char *prompt); + +PtTimestamp previous_callback_time = 0; + +int period; /* milliseconds per callback */ + +long histogram[HIST_LEN]; +long max_latency = 0; /* worst latency observed */ +long out_of_range = 0; /* how many points outside of HIST_LEN? */ + +int test_in, test_out; /* test MIDI in and/or out? */ +int output_period; /* output MIDI every __ iterations if test_out true */ +int iteration = 0; +PmStream *in, *out; +int note_on = 0; /* is the note currently on? */ + +/* callback function for PortTime -- computes histogram */ +void pt_callback(PtTimestamp timestamp, void *userData) +{ + PtTimestamp difference = timestamp - previous_callback_time - period; + previous_callback_time = timestamp; + + /* allow 5 seconds for the system to settle down */ + if (timestamp < 5000) return; + + iteration++; + /* send a note on/off if user requested it */ + if (test_out && (iteration % output_period == 0)) { + PmEvent buffer[1]; + buffer[0].timestamp = Pt_Time(NULL); + if (note_on) { + /* note off */ + buffer[0].message = Pm_Message(0x90, 60, 0); + note_on = 0; + } else { + /* note on */ + buffer[0].message = Pm_Message(0x90, 60, 100); + note_on = 1; + } + Pm_Write(out, buffer, 1); + iteration = 0; + } + + /* read all waiting events (if user requested) */ + if (test_in) { + PmError status; + PmEvent buffer[1]; + do { + status = Pm_Poll(in); + if (status == TRUE) { + Pm_Read(in,buffer,1); + } + } while (status == TRUE); + } + + if (difference < 0) return; /* ignore when system is "catching up" */ + + /* update the histogram */ + if (difference < HIST_LEN) { + histogram[difference]++; + } else { + out_of_range++; + } + + if (max_latency < difference) max_latency = difference; +} + + +int main() +{ + char line[STRING_MAX]; + int i; + int len; + int choice; + PtTimestamp stop; + printf("Latency histogram.\n"); + period = get_number("Choose timer period (in ms): "); + assert(period >= 1); + printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n", + "1. No MIDI traffic", + "2. MIDI input", + "3. MIDI output", + "4. MIDI input and output"); + choice = get_number("? "); + switch (choice) { + case 1: test_in = 0; test_out = 0; break; + case 2: test_in = 1; test_out = 0; break; + case 3: test_in = 0; test_out = 1; break; + case 4: test_in = 1; test_out = 1; break; + default: assert(0); + } + if (test_in || test_out) { + /* list device information */ + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if ((test_in && info->input) || + (test_out && info->output)) { + printf("%d: %s, %s", i, info->interf, info->name); + if (info->input) printf(" (input)"); + if (info->output) printf(" (output)"); + printf("\n"); + } + } + /* open stream(s) */ + if (test_in) { + int i = get_number("MIDI input device number: "); + Pm_OpenInput(&in, + i, + NULL, + INPUT_BUFFER_SIZE, + (long (*)(void *)) Pt_Time, + NULL); + /* turn on filtering; otherwise, input might overflow in the + 5-second period before timer callback starts reading midi */ + Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK); + } + if (test_out) { + int i = get_number("MIDI output device number: "); + PmEvent buffer[1]; + Pm_OpenOutput(&out, + i, + NULL, + OUTPUT_BUFFER_SIZE, + (long (*)(void *)) Pt_Time, + NULL, + 0); /* no latency scheduling */ + + /* send a program change to force a status byte -- this fixes + a problem with a buggy linux MidiSport driver, and shouldn't + hurt anything else + */ + buffer[0].timestamp = 0; + buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */ + Pm_Write(out, buffer, 1); + + output_period = get_number( + "MIDI out should be sent every __ callback iterations: "); + + assert(output_period >= 1); + } + } + + printf("%s%s", "Latency measurements will start in 5 seconds. ", + "Type return to stop: "); + Pt_Start(period, &pt_callback, 0); + fgets(line, STRING_MAX, stdin); + stop = Pt_Time(); + Pt_Stop(); + + /* courteously turn off the last note, if necessary */ + if (note_on) { + PmEvent buffer[1]; + buffer[0].timestamp = Pt_Time(NULL); + buffer[0].message = Pm_Message(0x90, 60, 0); + Pm_Write(out, buffer, 1); + } + + /* print the histogram */ + printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001); + printf("Latency(ms) Number of occurrences\n"); + /* avoid printing beyond last non-zero histogram entry */ + len = min(HIST_LEN, max_latency + 1); + for (i = 0; i < len; i++) { + printf("%2d %10ld\n", i, histogram[i]); + } + printf("Number of points greater than %dms: %ld\n", + HIST_LEN - 1, out_of_range); + printf("Maximum latency: %ld milliseconds\n", max_latency); + printf("\nNote that due to rounding, actual latency can be 1ms higher\n"); + printf("than the numbers reported here.\n"); + printf("Type return to exit..."); + fgets(line, STRING_MAX, stdin); + return 0; +} + + +/* read a number from console */ +int get_number(char *prompt) +{ + char line[STRING_MAX]; + int n = 0, i; + printf(prompt); + while (n != 1) { + n = scanf("%d", &i); + fgets(line, STRING_MAX, stdin); + + } + return i; +} -- cgit v1.2.1