| @@ -85,6 +85,8 @@ const char *SOURCE_SIGNAL_SEMAPHORE = "signal_semaphore"; | |||
| const char *SOURCE_WAIT_SEMAPHORE = "wait_semaphore"; | |||
| jack_client_t *client; | |||
| semaphore_t connect_semaphore; | |||
| volatile int connections_established; | |||
| const char *error_message; | |||
| const char *error_source; | |||
| jack_nframes_t highest_latency; | |||
| @@ -111,6 +113,8 @@ char *program_name; | |||
| jack_port_t *remote_in_port; | |||
| jack_port_t *remote_out_port; | |||
| size_t samples; | |||
| const char *target_in_port_name; | |||
| const char *target_out_port_name; | |||
| int timeout; | |||
| jack_nframes_t total_latency; | |||
| jack_time_t total_latency_time; | |||
| @@ -133,6 +137,11 @@ set_process_error(const char *source, const char *message); | |||
| static int | |||
| signal_semaphore(semaphore_t semaphore); | |||
| static jack_port_t * | |||
| update_connection(jack_port_t *remote_port, int connected, | |||
| jack_port_t *local_port, jack_port_t *current_port, | |||
| const char *target_name); | |||
| static int | |||
| wait_semaphore(semaphore_t semaphore, int block); | |||
| @@ -217,6 +226,53 @@ handle_info(const char *message) | |||
| /* Suppress info */ | |||
| } | |||
| static void | |||
| handle_port_connection_change(jack_port_id_t port_id_1, | |||
| jack_port_id_t port_id_2, int connected, | |||
| void *arg) | |||
| { | |||
| jack_port_t *port_1; | |||
| jack_port_t *port_2; | |||
| if ((remote_in_port != NULL) && (remote_out_port != NULL)) { | |||
| return; | |||
| } | |||
| port_1 = jack_port_by_id(client, port_id_1); | |||
| port_2 = jack_port_by_id(client, port_id_2); | |||
| /* The 'update_connection' call is not RT-safe. It calls | |||
| 'jack_port_get_connections' and 'jack_free'. This might be a problem | |||
| with JACK 1, as this callback runs in the process thread in JACK 1. */ | |||
| if (port_1 == in_port) { | |||
| remote_in_port = update_connection(port_2, connected, in_port, | |||
| remote_in_port, | |||
| target_in_port_name); | |||
| } else if (port_2 == in_port) { | |||
| remote_in_port = update_connection(port_1, connected, in_port, | |||
| remote_in_port, | |||
| target_in_port_name); | |||
| } else if (port_1 == out_port) { | |||
| remote_out_port = update_connection(port_2, connected, out_port, | |||
| remote_out_port, | |||
| target_out_port_name); | |||
| } else if (port_2 == out_port) { | |||
| remote_out_port = update_connection(port_1, connected, out_port, | |||
| remote_out_port, | |||
| target_out_port_name); | |||
| } | |||
| if ((remote_in_port != NULL) && (remote_out_port != NULL)) { | |||
| connections_established = 1; | |||
| if (! signal_semaphore(connect_semaphore)) { | |||
| /* Sigh ... */ | |||
| die("post_semaphore", get_semaphore_error()); | |||
| } | |||
| if (! signal_semaphore(init_semaphore)) { | |||
| /* Sigh ... */ | |||
| die("post_semaphore", get_semaphore_error()); | |||
| } | |||
| } | |||
| } | |||
| static int | |||
| handle_process(jack_nframes_t frames, void *arg) | |||
| { | |||
| @@ -342,6 +398,10 @@ static void | |||
| handle_signal(int sig) | |||
| { | |||
| process_state = -2; | |||
| if (! signal_semaphore(connect_semaphore)) { | |||
| /* Sigh ... */ | |||
| die(SOURCE_SIGNAL_SEMAPHORE, get_semaphore_error()); | |||
| } | |||
| if (! signal_semaphore(process_semaphore)) { | |||
| /* Sigh ... */ | |||
| die(SOURCE_SIGNAL_SEMAPHORE, get_semaphore_error()); | |||
| @@ -364,12 +424,14 @@ output_error(const char *source, const char *message) | |||
| static void | |||
| output_usage(void) | |||
| { | |||
| fprintf(stderr, "Usage: %s [options] out-port-name in-port-name\n\n" | |||
| fprintf(stderr, "Usage: %s [options] [out-port-name in-port-name]\n\n" | |||
| "\t-h, --help print program usage\n" | |||
| "\t-m, --message-size=size set size of MIDI messages to send\n" | |||
| "\t-s, --samples=n number of MIDI messages to send\n" | |||
| "\t-t, --timeout=seconds wait time before giving up on message\n" | |||
| "\n", program_name); | |||
| "\t-m, --message-size=size set size of MIDI messages to send " | |||
| "(default: 3)\n" | |||
| "\t-s, --samples=n number of MIDI messages to send " | |||
| "(default: 1024)\n" | |||
| "\t-t, --timeout=seconds message timeout (default: 5)\n\n", | |||
| program_name); | |||
| } | |||
| static unsigned long | |||
| @@ -445,6 +507,46 @@ signal_semaphore(semaphore_t semaphore) | |||
| } | |||
| static jack_port_t * | |||
| update_connection(jack_port_t *remote_port, int connected, | |||
| jack_port_t *local_port, jack_port_t *current_port, | |||
| const char *target_name) | |||
| { | |||
| if (connected) { | |||
| if (current_port) { | |||
| return current_port; | |||
| } | |||
| if (target_name) { | |||
| if (strcmp(target_name, jack_port_name(remote_port))) { | |||
| return NULL; | |||
| } | |||
| } | |||
| return remote_port; | |||
| } | |||
| if (! strcmp(jack_port_name(remote_port), jack_port_name(current_port))) { | |||
| const char **port_names; | |||
| if (target_name) { | |||
| return NULL; | |||
| } | |||
| port_names = jack_port_get_connections(local_port); | |||
| if (port_names == NULL) { | |||
| return NULL; | |||
| } | |||
| /* If a connected port is disconnected and other ports are still | |||
| connected, then we take the first port name in the array and use it | |||
| as our remote port. It's a dumb implementation. */ | |||
| current_port = jack_port_by_name(client, port_names[0]); | |||
| jack_free(port_names); | |||
| if (current_port == NULL) { | |||
| /* Sigh */ | |||
| die("jack_port_by_name", "failed to get port by name"); | |||
| } | |||
| } | |||
| return current_port; | |||
| } | |||
| static int | |||
| wait_semaphore(semaphore_t semaphore, int block) | |||
| { | |||
| @@ -494,11 +596,15 @@ main(int argc, char **argv) | |||
| {"samples", 1, NULL, 's'}, | |||
| {"timeout", 1, NULL, 't'} | |||
| }; | |||
| size_t name_arg_count; | |||
| char *option_string = "hm:s:t:"; | |||
| int show_usage = 0; | |||
| connections_established = 0; | |||
| error_message = NULL; | |||
| message_size = 3; | |||
| program_name = argv[0]; | |||
| remote_in_port = 0; | |||
| remote_out_port = 0; | |||
| samples = 1024; | |||
| timeout = 5; | |||
| @@ -536,7 +642,17 @@ main(int argc, char **argv) | |||
| } | |||
| } | |||
| parse_port_names: | |||
| if ((argc - optind) != 2) { | |||
| name_arg_count = argc - optind; | |||
| switch (name_arg_count) { | |||
| case 2: | |||
| target_in_port_name = argv[optind + 1]; | |||
| target_out_port_name = argv[optind]; | |||
| break; | |||
| case 0: | |||
| target_in_port_name = 0; | |||
| target_out_port_name = 0; | |||
| break; | |||
| default: | |||
| output_usage(); | |||
| return EXIT_FAILURE; | |||
| } | |||
| @@ -599,18 +715,6 @@ main(int argc, char **argv) | |||
| error_source = "jack_client_open"; | |||
| goto free_message_2; | |||
| } | |||
| remote_in_port = jack_port_by_name(client, argv[optind + 1]); | |||
| if (remote_in_port == NULL) { | |||
| error_message = "invalid port name"; | |||
| error_source = argv[optind + 1]; | |||
| goto close_client; | |||
| } | |||
| remote_out_port = jack_port_by_name(client, argv[optind]); | |||
| if (remote_out_port == NULL) { | |||
| error_message = "invalid port name"; | |||
| error_source = argv[optind]; | |||
| goto close_client; | |||
| } | |||
| in_port = jack_port_register(client, "in", JACK_DEFAULT_MIDI_TYPE, | |||
| JackPortIsInput, 0); | |||
| if (in_port == NULL) { | |||
| @@ -635,16 +739,29 @@ main(int argc, char **argv) | |||
| error_source = "jack_set_xrun_callback"; | |||
| goto unregister_out_port; | |||
| } | |||
| if (jack_set_port_connect_callback(client, handle_port_connection_change, | |||
| NULL)) { | |||
| error_message = "failed to set port connection callback"; | |||
| error_source = "jack_set_port_connect_callback"; | |||
| goto unregister_out_port; | |||
| } | |||
| jack_on_shutdown(client, handle_shutdown, NULL); | |||
| jack_set_info_function(handle_info); | |||
| process_state = 0; | |||
| init_semaphore = create_semaphore(0); | |||
| if (init_semaphore == NULL) { | |||
| connect_semaphore = create_semaphore(0); | |||
| if (connect_semaphore == NULL) { | |||
| error_message = get_semaphore_error(); | |||
| error_source = "create_semaphore"; | |||
| goto unregister_out_port; | |||
| } | |||
| process_semaphore = create_semaphore(1); | |||
| init_semaphore = create_semaphore(1); | |||
| if (init_semaphore == NULL) { | |||
| error_message = get_semaphore_error(); | |||
| error_source = "create_semaphore"; | |||
| goto destroy_connect_semaphore;; | |||
| } | |||
| process_semaphore = create_semaphore(2); | |||
| if (process_semaphore == NULL) { | |||
| error_message = get_semaphore_error(); | |||
| error_source = "create_semaphore"; | |||
| @@ -655,33 +772,39 @@ main(int argc, char **argv) | |||
| error_source = "jack_activate"; | |||
| goto destroy_process_semaphore; | |||
| } | |||
| if (jack_connect(client, jack_port_name(out_port), | |||
| jack_port_name(remote_out_port))) { | |||
| error_message = "could not connect MIDI out port"; | |||
| error_source = "jack_connect"; | |||
| goto deactivate_client; | |||
| } | |||
| if (jack_connect(client, jack_port_name(remote_in_port), | |||
| jack_port_name(in_port))) { | |||
| error_message = "could not connect MIDI in port"; | |||
| error_source = "jack_connect"; | |||
| goto deactivate_client; | |||
| } | |||
| if (! signal_semaphore(init_semaphore)) { | |||
| error_message = get_semaphore_error(); | |||
| error_source = "post_semaphore"; | |||
| goto deactivate_client; | |||
| if (name_arg_count) { | |||
| if (jack_connect(client, jack_port_name(out_port), | |||
| target_out_port_name)) { | |||
| error_message = "could not connect MIDI out port"; | |||
| error_source = "jack_connect"; | |||
| goto deactivate_client; | |||
| } | |||
| if (jack_connect(client, target_in_port_name, | |||
| jack_port_name(in_port))) { | |||
| error_message = "could not connect MIDI in port"; | |||
| error_source = "jack_connect"; | |||
| goto deactivate_client; | |||
| } | |||
| } | |||
| if (! register_signal_handler(handle_signal)) { | |||
| error_message = strerror(errno); | |||
| error_source = "register_signal_handler"; | |||
| goto deactivate_client; | |||
| } | |||
| if (wait_semaphore(process_semaphore, 1) == -1) { | |||
| printf("Waiting for connections ...\n"); | |||
| if (wait_semaphore(connect_semaphore, 1) == -1) { | |||
| error_message = get_semaphore_error(); | |||
| error_source = "wait_semaphore"; | |||
| goto deactivate_client; | |||
| } | |||
| if (connections_established) { | |||
| printf("Waiting for test completion ...\n\n"); | |||
| if (wait_semaphore(process_semaphore, 1) == -1) { | |||
| error_message = get_semaphore_error(); | |||
| error_source = "wait_semaphore"; | |||
| goto deactivate_client; | |||
| } | |||
| } | |||
| if (! register_signal_handler(SIG_DFL)) { | |||
| error_message = strerror(errno); | |||
| error_source = "register_signal_handler"; | |||
| @@ -766,24 +889,20 @@ main(int argc, char **argv) | |||
| } | |||
| deactivate_client: | |||
| jack_deactivate(client); | |||
| /* Output this information after deactivation to prevent two threads | |||
| from accessing data at the same time. */ | |||
| if (process_state != 2) { | |||
| printf("\nMessages sent: %d\nMessages received: %d\n", messages_sent, | |||
| messages_received); | |||
| } | |||
| printf("\nMessages sent: %d\nMessages received: %d\n", messages_sent, | |||
| messages_received); | |||
| if (unexpected_messages) { | |||
| printf("Unexpected messages received: %d\n", unexpected_messages); | |||
| } | |||
| if (xrun_count) { | |||
| printf("Xruns: %d\n", xrun_count); | |||
| } | |||
| destroy_process_semaphore: | |||
| destroy_semaphore(process_semaphore, 1); | |||
| destroy_semaphore(process_semaphore, 2); | |||
| destroy_init_semaphore: | |||
| destroy_semaphore(init_semaphore, 0); | |||
| destroy_semaphore(init_semaphore, 1); | |||
| destroy_connect_semaphore: | |||
| destroy_semaphore(connect_semaphore, 0); | |||
| unregister_out_port: | |||
| jack_port_unregister(client, out_port); | |||
| unregister_in_port: | |||