Merge pull request #23 from jackaudio/device_reservation_fixes
[jack2.git] / example-clients / midi_latency_test.c
blob39a3681338ab2a183ab1a30ce04b90fff08ab783
1 /*
2 Copyright (C) 2010 Devin Anderson
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as published by
6 the Free Software Foundation; either version 2.1 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * This program is used to measure MIDI latency and jitter. It writes MIDI
22 * messages to one port and calculates how long it takes before it reads the
23 * same MIDI message over another port. It was written to calculate the
24 * latency and jitter of hardware and JACK hardware drivers, but might have
25 * other practical applications.
27 * The latency results of the program include the latency introduced by the
28 * JACK system. Because JACK has sample accurate MIDI, the same latency
29 * imposed on audio is also imposed on MIDI going through the system. Make
30 * sure you take this into account before complaining to me or (*especially*)
31 * other JACK developers about reported MIDI latency.
33 * The jitter results are a little more interesting. The program attempts to
34 * calculate 'average jitter' and 'peak jitter', as defined here:
36 * http://openmuse.org/transport/fidelity.html
38 * It also outputs a jitter plot, which gives you a more specific idea about
39 * the MIDI jitter for the ports you're testing. This is useful for catching
40 * extreme jitter values, and for analyzing the amount of truth in the
41 * technical specifications for your MIDI interface(s). :)
43 * This program is loosely based on 'alsa-midi-latency-test' in the ALSA test
44 * suite.
46 * To port this program to non-POSIX platforms, you'll have to include
47 * implementations for semaphores and command-line argument handling.
50 #include <assert.h>
51 #include <errno.h>
52 #include <math.h>
53 #include <signal.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
58 #include <getopt.h>
60 #include <jack/jack.h>
61 #include <jack/midiport.h>
63 #ifdef WIN32
64 #include <windows.h>
65 #include <unistd.h>
66 #else
67 #include <semaphore.h>
68 #endif
70 #define ABS(x) (((x) >= 0) ? (x) : (-(x)))
72 #ifdef WIN32
73 typedef HANDLE semaphore_t;
74 #else
75 typedef sem_t *semaphore_t;
76 #endif
78 const char *ERROR_MSG_TIMEOUT = "timed out while waiting for MIDI message";
79 const char *ERROR_RESERVE = "could not reserve MIDI event on port buffer";
80 const char *ERROR_SHUTDOWN = "the JACK server has been shutdown";
82 const char *SOURCE_EVENT_RESERVE = "jack_midi_event_reserve";
83 const char *SOURCE_PROCESS = "handle_process";
84 const char *SOURCE_SHUTDOWN = "handle_shutdown";
85 const char *SOURCE_SIGNAL_SEMAPHORE = "signal_semaphore";
86 const char *SOURCE_WAIT_SEMAPHORE = "wait_semaphore";
88 char *alias1;
89 char *alias2;
90 jack_client_t *client;
91 semaphore_t connect_semaphore;
92 volatile int connections_established;
93 const char *error_message;
94 const char *error_source;
95 jack_nframes_t highest_latency;
96 jack_time_t highest_latency_time;
97 jack_latency_range_t in_latency_range;
98 jack_port_t *in_port;
99 semaphore_t init_semaphore;
100 jack_nframes_t last_activity;
101 jack_time_t last_activity_time;
102 jack_time_t *latency_time_values;
103 jack_nframes_t *latency_values;
104 jack_nframes_t lowest_latency;
105 jack_time_t lowest_latency_time;
106 jack_midi_data_t *message_1;
107 jack_midi_data_t *message_2;
108 int messages_received;
109 int messages_sent;
110 size_t message_size;
111 jack_latency_range_t out_latency_range;
112 jack_port_t *out_port;
113 semaphore_t process_semaphore;
114 volatile sig_atomic_t process_state;
115 char *program_name;
116 jack_port_t *remote_in_port;
117 jack_port_t *remote_out_port;
118 size_t samples;
119 const char *target_in_port_name;
120 const char *target_out_port_name;
121 int timeout;
122 jack_nframes_t total_latency;
123 jack_time_t total_latency_time;
124 int unexpected_messages;
125 int xrun_count;
127 #ifdef WIN32
128 char semaphore_error_msg[1024];
129 #endif
131 static void
132 output_error(const char *source, const char *message);
134 static void
135 output_usage(void);
137 static void
138 set_process_error(const char *source, const char *message);
140 static int
141 signal_semaphore(semaphore_t semaphore);
143 static jack_port_t *
144 update_connection(jack_port_t *remote_port, int connected,
145 jack_port_t *local_port, jack_port_t *current_port,
146 const char *target_name);
148 static int
149 wait_semaphore(semaphore_t semaphore, int block);
151 static semaphore_t
152 create_semaphore(int id)
154 semaphore_t semaphore;
156 #ifdef WIN32
157 semaphore = CreateSemaphore(NULL, 0, 2, NULL);
158 #elif defined (__APPLE__)
159 char name[128];
160 sprintf(name, "midi_sem_%d", id);
161 semaphore = sem_open(name, O_CREAT, 0777, 0);
162 if (semaphore == (sem_t *) SEM_FAILED) {
163 semaphore = NULL;
165 #else
166 semaphore = malloc(sizeof(semaphore_t));
167 if (semaphore != NULL) {
168 if (sem_init(semaphore, 0, 0)) {
169 free(semaphore);
170 semaphore = NULL;
173 #endif
175 return semaphore;
178 static void
179 destroy_semaphore(semaphore_t semaphore, int id)
182 #ifdef WIN32
183 CloseHandle(semaphore);
184 #else
185 sem_destroy(semaphore);
186 #ifdef __APPLE__
188 char name[128];
189 sprintf(name, "midi_sem_%d", id);
190 sem_close(semaphore);
191 sem_unlink(name);
193 #else
194 free(semaphore);
195 #endif
196 #endif
200 static void
201 die(const char *source, const char *error_message)
203 output_error(source, error_message);
204 output_usage();
205 exit(EXIT_FAILURE);
208 static const char *
209 get_semaphore_error(void)
212 #ifdef WIN32
213 DWORD error = GetLastError();
214 if (! FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error,
215 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
216 semaphore_error_msg, 1024, NULL)) {
217 snprintf(semaphore_error_msg, 1023, "Unknown OS error code '%ld'",
218 error);
220 return semaphore_error_msg;
221 #else
222 return strerror(errno);
223 #endif
227 static void
228 handle_info(const char *message)
230 /* Suppress info */
233 static void
234 handle_port_connection_change(jack_port_id_t port_id_1,
235 jack_port_id_t port_id_2, int connected,
236 void *arg)
238 jack_port_t *port_1;
239 jack_port_t *port_2;
240 if ((remote_in_port != NULL) && (remote_out_port != NULL)) {
241 return;
243 port_1 = jack_port_by_id(client, port_id_1);
244 port_2 = jack_port_by_id(client, port_id_2);
246 /* The 'update_connection' call is not RT-safe. It calls
247 'jack_port_get_connections' and 'jack_free'. This might be a problem
248 with JACK 1, as this callback runs in the process thread in JACK 1. */
250 if (port_1 == in_port) {
251 remote_in_port = update_connection(port_2, connected, in_port,
252 remote_in_port,
253 target_in_port_name);
254 } else if (port_2 == in_port) {
255 remote_in_port = update_connection(port_1, connected, in_port,
256 remote_in_port,
257 target_in_port_name);
258 } else if (port_1 == out_port) {
259 remote_out_port = update_connection(port_2, connected, out_port,
260 remote_out_port,
261 target_out_port_name);
262 } else if (port_2 == out_port) {
263 remote_out_port = update_connection(port_1, connected, out_port,
264 remote_out_port,
265 target_out_port_name);
267 if ((remote_in_port != NULL) && (remote_out_port != NULL)) {
268 connections_established = 1;
269 if (! signal_semaphore(connect_semaphore)) {
270 /* Sigh ... */
271 die("post_semaphore", get_semaphore_error());
273 if (! signal_semaphore(init_semaphore)) {
274 /* Sigh ... */
275 die("post_semaphore", get_semaphore_error());
280 static int
281 handle_process(jack_nframes_t frames, void *arg)
283 jack_midi_data_t *buffer;
284 jack_midi_event_t event;
285 jack_nframes_t event_count;
286 jack_nframes_t event_time;
287 jack_nframes_t frame;
288 size_t i;
289 jack_nframes_t last_frame_time;
290 jack_midi_data_t *message;
291 jack_time_t microseconds;
292 void *port_buffer;
293 jack_time_t time;
294 jack_midi_clear_buffer(jack_port_get_buffer(out_port, frames));
295 switch (process_state) {
297 case 0:
298 /* State: initializing */
299 switch (wait_semaphore(init_semaphore, 0)) {
300 case -1:
301 set_process_error(SOURCE_WAIT_SEMAPHORE, get_semaphore_error());
302 /* Fallthrough on purpose */
303 case 0:
304 return 0;
306 highest_latency = 0;
307 lowest_latency = 0;
308 messages_received = 0;
309 messages_sent = 0;
310 process_state = 1;
311 total_latency = 0;
312 total_latency_time = 0;
313 unexpected_messages = 0;
314 xrun_count = 0;
315 jack_port_get_latency_range(remote_in_port, JackCaptureLatency,
316 &in_latency_range);
317 jack_port_get_latency_range(remote_out_port, JackPlaybackLatency,
318 &out_latency_range);
319 goto send_message;
321 case 1:
322 /* State: processing */
323 port_buffer = jack_port_get_buffer(in_port, frames);
324 event_count = jack_midi_get_event_count(port_buffer);
325 last_frame_time = jack_last_frame_time(client);
326 for (i = 0; i < event_count; i++) {
327 jack_midi_event_get(&event, port_buffer, i);
328 message = (messages_received % 2) ? message_2 : message_1;
329 if ((event.size == message_size) &&
330 (! memcmp(message, event.buffer,
331 message_size * sizeof(jack_midi_data_t)))) {
332 goto found_message;
334 unexpected_messages++;
336 microseconds = jack_frames_to_time(client, last_frame_time) -
337 last_activity_time;
338 if ((microseconds / 1000000) >= timeout) {
339 set_process_error(SOURCE_PROCESS, ERROR_MSG_TIMEOUT);
341 break;
342 found_message:
343 event_time = last_frame_time + event.time;
344 frame = event_time - last_activity;
345 time = jack_frames_to_time(client, event_time) - last_activity_time;
346 if ((! highest_latency) || (frame > highest_latency)) {
347 highest_latency = frame;
348 highest_latency_time = time;
350 if ((! lowest_latency) || (frame < lowest_latency)) {
351 lowest_latency = frame;
352 lowest_latency_time = time;
354 latency_time_values[messages_received] = time;
355 latency_values[messages_received] = frame;
356 total_latency += frame;
357 total_latency_time += time;
358 messages_received++;
359 if (messages_received == samples) {
360 process_state = 2;
361 if (! signal_semaphore(process_semaphore)) {
362 /* Sigh ... */
363 die(SOURCE_SIGNAL_SEMAPHORE, get_semaphore_error());
365 break;
367 send_message:
368 frame = (jack_nframes_t) ((((double) rand()) / RAND_MAX) * frames);
369 if (frame >= frames) {
370 frame = frames - 1;
372 port_buffer = jack_port_get_buffer(out_port, frames);
373 buffer = jack_midi_event_reserve(port_buffer, frame, message_size);
374 if (buffer == NULL) {
375 set_process_error(SOURCE_EVENT_RESERVE, ERROR_RESERVE);
376 break;
378 message = (messages_sent % 2) ? message_2 : message_1;
379 memcpy(buffer, message, message_size * sizeof(jack_midi_data_t));
380 last_activity = jack_last_frame_time(client) + frame;
381 last_activity_time = jack_frames_to_time(client, last_activity);
382 messages_sent++;
384 case 2:
385 /* State: finished - do nothing */
386 case -1:
387 /* State: error - do nothing */
388 case -2:
389 /* State: signalled - do nothing */
392 return 0;
395 static void
396 handle_shutdown(void *arg)
398 set_process_error(SOURCE_SHUTDOWN, ERROR_SHUTDOWN);
401 static void
402 handle_signal(int sig)
404 process_state = -2;
405 if (! signal_semaphore(connect_semaphore)) {
406 /* Sigh ... */
407 die(SOURCE_SIGNAL_SEMAPHORE, get_semaphore_error());
409 if (! signal_semaphore(process_semaphore)) {
410 /* Sigh ... */
411 die(SOURCE_SIGNAL_SEMAPHORE, get_semaphore_error());
415 static int
416 handle_xrun(void *arg)
418 xrun_count++;
419 return 0;
422 static void
423 output_error(const char *source, const char *message)
425 fprintf(stderr, "%s: %s: %s\n", program_name, source, message);
428 static void
429 output_usage(void)
431 fprintf(stderr, "Usage: %s [options] [out-port-name in-port-name]\n\n"
432 "\t-h, --help print program usage\n"
433 "\t-m, --message-size=size set size of MIDI messages to send "
434 "(default: 3)\n"
435 "\t-s, --samples=n number of MIDI messages to send "
436 "(default: 1024)\n"
437 "\t-t, --timeout=seconds message timeout (default: 5)\n\n",
438 program_name);
441 static unsigned long
442 parse_positive_number_arg(char *s, char *name)
444 char *end_ptr;
445 unsigned long result;
446 errno = 0;
447 result = strtoul(s, &end_ptr, 10);
448 if (errno) {
449 die(name, strerror(errno));
451 if (*s == '\0') {
452 die(name, "argument value cannot be empty");
454 if (*end_ptr != '\0') {
455 die(name, "invalid value");
457 if (! result) {
458 die(name, "must be a positive number");
460 return result;
463 static int
464 register_signal_handler(void (*func)(int))
467 #ifdef WIN32
468 if (signal(SIGABRT, func) == SIG_ERR) {
469 return 0;
471 #else
472 if (signal(SIGQUIT, func) == SIG_ERR) {
473 return 0;
475 if (signal(SIGHUP, func) == SIG_ERR) {
476 return 0;
478 #endif
480 if (signal(SIGINT, func) == SIG_ERR) {
481 return 0;
483 if (signal(SIGTERM, func) == SIG_ERR) {
484 return 0;
486 return 1;
489 static void
490 set_process_error(const char *source, const char *message)
492 error_source = source;
493 error_message = message;
494 process_state = -1;
495 if (! signal_semaphore(process_semaphore)) {
496 /* Sigh ... */
497 output_error(source, message);
498 die(SOURCE_SIGNAL_SEMAPHORE, get_semaphore_error());
502 static int
503 signal_semaphore(semaphore_t semaphore)
506 #ifdef WIN32
507 return ReleaseSemaphore(semaphore, 1, NULL);
508 #else
509 return ! sem_post(semaphore);
510 #endif
514 static jack_port_t *
515 update_connection(jack_port_t *remote_port, int connected,
516 jack_port_t *local_port, jack_port_t *current_port,
517 const char *target_name)
519 if (connected) {
520 if (current_port) {
521 return current_port;
523 if (target_name) {
524 char *aliases[2];
525 if (! strcmp(target_name, jack_port_name(remote_port))) {
526 return remote_port;
528 aliases[0] = alias1;
529 aliases[1] = alias2;
530 switch (jack_port_get_aliases(remote_port, aliases)) {
531 case -1:
532 /* Sigh ... */
533 die("jack_port_get_aliases", "Failed to get port aliases");
534 case 2:
535 if (! strcmp(target_name, alias2)) {
536 return remote_port;
538 /* Fallthrough on purpose */
539 case 1:
540 if (! strcmp(target_name, alias1)) {
541 return remote_port;
543 /* Fallthrough on purpose */
544 case 0:
545 return NULL;
547 /* This shouldn't happen. */
548 assert(0);
550 return remote_port;
552 if (! strcmp(jack_port_name(remote_port), jack_port_name(current_port))) {
553 const char **port_names;
554 if (target_name) {
555 return NULL;
557 port_names = jack_port_get_connections(local_port);
558 if (port_names == NULL) {
559 return NULL;
562 /* If a connected port is disconnected and other ports are still
563 connected, then we take the first port name in the array and use it
564 as our remote port. It's a dumb implementation. */
565 current_port = jack_port_by_name(client, port_names[0]);
566 jack_free(port_names);
567 if (current_port == NULL) {
568 /* Sigh */
569 die("jack_port_by_name", "failed to get port by name");
572 return current_port;
575 static int
576 wait_semaphore(semaphore_t semaphore, int block)
579 #ifdef WIN32
580 DWORD result = WaitForSingleObject(semaphore, block ? INFINITE : 0);
581 switch (result) {
582 case WAIT_OBJECT_0:
583 return 1;
584 case WAIT_TIMEOUT:
585 return 0;
587 return -1;
588 #else
589 if (block) {
590 while (sem_wait(semaphore)) {
591 if (errno != EINTR) {
592 return -1;
595 } else {
596 while (sem_trywait(semaphore)) {
597 switch (errno) {
598 case EAGAIN:
599 return 0;
600 case EINTR:
601 continue;
602 default:
603 return -1;
607 return 1;
608 #endif
613 main(int argc, char **argv)
615 int jitter_plot[101];
616 int latency_plot[101];
617 int long_index = 0;
618 struct option long_options[] = {
619 {"help", 0, NULL, 'h'},
620 {"message-size", 1, NULL, 'm'},
621 {"samples", 1, NULL, 's'},
622 {"timeout", 1, NULL, 't'}
624 size_t name_arg_count;
625 size_t name_size;
626 char *option_string = "hm:s:t:";
627 int show_usage = 0;
628 connections_established = 0;
629 error_message = NULL;
630 message_size = 3;
631 program_name = argv[0];
632 remote_in_port = 0;
633 remote_out_port = 0;
634 samples = 1024;
635 timeout = 5;
637 for (;;) {
638 char c = getopt_long(argc, argv, option_string, long_options,
639 &long_index);
640 switch (c) {
641 case 'h':
642 show_usage = 1;
643 break;
644 case 'm':
645 message_size = parse_positive_number_arg(optarg, "message-size");
646 break;
647 case 's':
648 samples = parse_positive_number_arg(optarg, "samples");
649 break;
650 case 't':
651 timeout = parse_positive_number_arg(optarg, "timeout");
652 break;
653 default:
655 char *s = "'- '";
656 s[2] = c;
657 die(s, "invalid switch");
659 case -1:
660 if (show_usage) {
661 output_usage();
662 exit(EXIT_SUCCESS);
664 goto parse_port_names;
665 case 1:
666 /* end of switch :) */
670 parse_port_names:
671 name_arg_count = argc - optind;
672 switch (name_arg_count) {
673 case 2:
674 target_in_port_name = argv[optind + 1];
675 target_out_port_name = argv[optind];
676 break;
677 case 0:
678 target_in_port_name = 0;
679 target_out_port_name = 0;
680 break;
681 default:
682 output_usage();
683 return EXIT_FAILURE;
685 name_size = jack_port_name_size();
686 alias1 = malloc(name_size * sizeof(char));
687 if (alias1 == NULL) {
688 error_message = strerror(errno);
689 error_source = "malloc";
690 goto show_error;
692 alias2 = malloc(name_size * sizeof(char));
693 if (alias2 == NULL) {
694 error_message = strerror(errno);
695 error_source = "malloc";
696 goto free_alias1;
698 latency_values = malloc(sizeof(jack_nframes_t) * samples);
699 if (latency_values == NULL) {
700 error_message = strerror(errno);
701 error_source = "malloc";
702 goto free_alias2;
704 latency_time_values = malloc(sizeof(jack_time_t) * samples);
705 if (latency_time_values == NULL) {
706 error_message = strerror(errno);
707 error_source = "malloc";
708 goto free_latency_values;
710 message_1 = malloc(message_size * sizeof(jack_midi_data_t));
711 if (message_1 == NULL) {
712 error_message = strerror(errno);
713 error_source = "malloc";
714 goto free_latency_time_values;
716 message_2 = malloc(message_size * sizeof(jack_midi_data_t));
717 if (message_2 == NULL) {
718 error_message = strerror(errno);
719 error_source = "malloc";
720 goto free_message_1;
722 switch (message_size) {
723 case 1:
724 message_1[0] = 0xf6;
725 message_2[0] = 0xfe;
726 break;
727 case 2:
728 message_1[0] = 0xc0;
729 message_1[1] = 0x00;
730 message_2[0] = 0xd0;
731 message_2[1] = 0x7f;
732 break;
733 case 3:
734 message_1[0] = 0x80;
735 message_1[1] = 0x00;
736 message_1[2] = 0x00;
737 message_2[0] = 0x90;
738 message_2[1] = 0x7f;
739 message_2[2] = 0x7f;
740 break;
741 default:
742 message_1[0] = 0xf0;
743 memset(message_1 + 1, 0,
744 (message_size - 2) * sizeof(jack_midi_data_t));
745 message_1[message_size - 1] = 0xf7;
746 message_2[0] = 0xf0;
747 memset(message_2 + 1, 0x7f,
748 (message_size - 2) * sizeof(jack_midi_data_t));
749 message_2[message_size - 1] = 0xf7;
751 client = jack_client_open(program_name, JackNullOption, NULL);
752 if (client == NULL) {
753 error_message = "failed to open JACK client";
754 error_source = "jack_client_open";
755 goto free_message_2;
757 in_port = jack_port_register(client, "in", JACK_DEFAULT_MIDI_TYPE,
758 JackPortIsInput, 0);
759 if (in_port == NULL) {
760 error_message = "failed to register MIDI-in port";
761 error_source = "jack_port_register";
762 goto close_client;
764 out_port = jack_port_register(client, "out", JACK_DEFAULT_MIDI_TYPE,
765 JackPortIsOutput, 0);
766 if (out_port == NULL) {
767 error_message = "failed to register MIDI-out port";
768 error_source = "jack_port_register";
769 goto unregister_in_port;
771 if (jack_set_process_callback(client, handle_process, NULL)) {
772 error_message = "failed to set process callback";
773 error_source = "jack_set_process_callback";
774 goto unregister_out_port;
776 if (jack_set_xrun_callback(client, handle_xrun, NULL)) {
777 error_message = "failed to set xrun callback";
778 error_source = "jack_set_xrun_callback";
779 goto unregister_out_port;
781 if (jack_set_port_connect_callback(client, handle_port_connection_change,
782 NULL)) {
783 error_message = "failed to set port connection callback";
784 error_source = "jack_set_port_connect_callback";
785 goto unregister_out_port;
787 jack_on_shutdown(client, handle_shutdown, NULL);
788 jack_set_info_function(handle_info);
789 process_state = 0;
791 connect_semaphore = create_semaphore(0);
792 if (connect_semaphore == NULL) {
793 error_message = get_semaphore_error();
794 error_source = "create_semaphore";
795 goto unregister_out_port;
797 init_semaphore = create_semaphore(1);
798 if (init_semaphore == NULL) {
799 error_message = get_semaphore_error();
800 error_source = "create_semaphore";
801 goto destroy_connect_semaphore;;
803 process_semaphore = create_semaphore(2);
804 if (process_semaphore == NULL) {
805 error_message = get_semaphore_error();
806 error_source = "create_semaphore";
807 goto destroy_init_semaphore;
809 if (jack_activate(client)) {
810 error_message = "could not activate client";
811 error_source = "jack_activate";
812 goto destroy_process_semaphore;
814 if (name_arg_count) {
815 if (jack_connect(client, jack_port_name(out_port),
816 target_out_port_name)) {
817 error_message = "could not connect MIDI out port";
818 error_source = "jack_connect";
819 goto deactivate_client;
821 if (jack_connect(client, target_in_port_name,
822 jack_port_name(in_port))) {
823 error_message = "could not connect MIDI in port";
824 error_source = "jack_connect";
825 goto deactivate_client;
828 if (! register_signal_handler(handle_signal)) {
829 error_message = strerror(errno);
830 error_source = "register_signal_handler";
831 goto deactivate_client;
833 printf("Waiting for connections ...\n");
834 if (wait_semaphore(connect_semaphore, 1) == -1) {
835 error_message = get_semaphore_error();
836 error_source = "wait_semaphore";
837 goto deactivate_client;
839 if (connections_established) {
840 printf("Waiting for test completion ...\n\n");
841 if (wait_semaphore(process_semaphore, 1) == -1) {
842 error_message = get_semaphore_error();
843 error_source = "wait_semaphore";
844 goto deactivate_client;
847 if (! register_signal_handler(SIG_DFL)) {
848 error_message = strerror(errno);
849 error_source = "register_signal_handler";
850 goto deactivate_client;
852 if (process_state == 2) {
853 double average_latency = ((double) total_latency) / samples;
854 double average_latency_time = total_latency_time / samples;
855 size_t i;
856 double latency_plot_offset =
857 floor(((double) lowest_latency_time) / 100.0) / 10.0;
858 double sample_rate = (double) jack_get_sample_rate(client);
859 jack_nframes_t total_jitter = 0;
860 jack_time_t total_jitter_time = 0;
861 for (i = 0; i <= 100; i++) {
862 jitter_plot[i] = 0;
863 latency_plot[i] = 0;
865 for (i = 0; i < samples; i++) {
866 double latency_time_value = (double) latency_time_values[i];
867 double latency_plot_time =
868 (latency_time_value / 1000.0) - latency_plot_offset;
869 double jitter_time = ABS(average_latency_time -
870 latency_time_value);
871 if (latency_plot_time >= 10.0) {
872 (latency_plot[100])++;
873 } else {
874 (latency_plot[(int) (latency_plot_time * 10.0)])++;
876 if (jitter_time >= 10000.0) {
877 (jitter_plot[100])++;
878 } else {
879 (jitter_plot[(int) (jitter_time / 100.0)])++;
881 total_jitter += ABS(average_latency -
882 ((double) latency_values[i]));
883 total_jitter_time += jitter_time;
885 printf("Reported out-port latency: %.2f-%.2f ms (%u-%u frames)\n"
886 "Reported in-port latency: %.2f-%.2f ms (%u-%u frames)\n"
887 "Average latency: %.2f ms (%.2f frames)\n"
888 "Lowest latency: %.2f ms (%u frames)\n"
889 "Highest latency: %.2f ms (%u frames)\n"
890 "Peak MIDI jitter: %.2f ms (%u frames)\n"
891 "Average MIDI jitter: %.2f ms (%.2f frames)\n",
892 (out_latency_range.min / sample_rate) * 1000.0,
893 (out_latency_range.max / sample_rate) * 1000.0,
894 out_latency_range.min, out_latency_range.max,
895 (in_latency_range.min / sample_rate) * 1000.0,
896 (in_latency_range.max / sample_rate) * 1000.0,
897 in_latency_range.min, in_latency_range.max,
898 average_latency_time / 1000.0, average_latency,
899 lowest_latency_time / 1000.0, lowest_latency,
900 highest_latency_time / 1000.0, highest_latency,
901 (highest_latency_time - lowest_latency_time) / 1000.0,
902 highest_latency - lowest_latency,
903 (total_jitter_time / 1000.0) / samples,
904 ((double) total_jitter) / samples);
905 printf("\nJitter Plot:\n");
906 for (i = 0; i < 100; i++) {
907 if (jitter_plot[i]) {
908 printf("%.1f - %.1f ms: %d\n", ((float) i) / 10.0,
909 ((float) (i + 1)) / 10.0, jitter_plot[i]);
912 if (jitter_plot[100]) {
913 printf(" > 10 ms: %d\n", jitter_plot[100]);
915 printf("\nLatency Plot:\n");
916 for (i = 0; i < 100; i++) {
917 if (latency_plot[i]) {
918 printf("%.1f - %.1f ms: %d\n",
919 latency_plot_offset + (((float) i) / 10.0),
920 latency_plot_offset + (((float) (i + 1)) / 10.0),
921 latency_plot[i]);
924 if (latency_plot[100]) {
925 printf(" > %.1f ms: %d\n", latency_plot_offset + 10.0,
926 latency_plot[100]);
929 deactivate_client:
930 jack_deactivate(client);
931 printf("\nMessages sent: %d\nMessages received: %d\n", messages_sent,
932 messages_received);
933 if (unexpected_messages) {
934 printf("Unexpected messages received: %d\n", unexpected_messages);
936 if (xrun_count) {
937 printf("Xruns: %d\n", xrun_count);
939 destroy_process_semaphore:
940 destroy_semaphore(process_semaphore, 2);
941 destroy_init_semaphore:
942 destroy_semaphore(init_semaphore, 1);
943 destroy_connect_semaphore:
944 destroy_semaphore(connect_semaphore, 0);
945 unregister_out_port:
946 jack_port_unregister(client, out_port);
947 unregister_in_port:
948 jack_port_unregister(client, in_port);
949 close_client:
950 jack_client_close(client);
951 free_message_2:
952 free(message_2);
953 free_message_1:
954 free(message_1);
955 free_latency_time_values:
956 free(latency_time_values);
957 free_latency_values:
958 free(latency_values);
959 free_alias2:
960 free(alias2);
961 free_alias1:
962 free(alias1);
963 if (error_message != NULL) {
964 show_error:
965 output_error(error_source, error_message);
966 exit(EXIT_FAILURE);
968 return EXIT_SUCCESS;