diff --git a/example-clients/midi_latency_test.c b/example-clients/midi_latency_test.c index feb24609..f5b274f7 100644 --- a/example-clients/midi_latency_test.c +++ b/example-clients/midi_latency_test.c @@ -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: diff --git a/windows/winmme/JackWinMMEDriver.cpp b/windows/winmme/JackWinMMEDriver.cpp index f7793363..b98158f7 100644 --- a/windows/winmme/JackWinMMEDriver.cpp +++ b/windows/winmme/JackWinMMEDriver.cpp @@ -32,6 +32,7 @@ JackWinMMEDriver::JackWinMMEDriver(const char *name, const char *alias, fPlaybackChannels = 0; input_ports = 0; output_ports = 0; + period = 0; } JackWinMMEDriver::~JackWinMMEDriver() @@ -119,6 +120,13 @@ JackWinMMEDriver::Close() delete[] output_ports; output_ports = 0; } + if (period) { + if (timeEndPeriod(period) != TIMERR_NOERROR) { + jack_error("JackWinMMEDriver::Close - failed to unset timer " + "resolution."); + result = -1; + } + } return result; } @@ -139,13 +147,32 @@ JackWinMMEDriver::Open(bool capturing, bool playing, int in_channels, jack_info("JackWinMMEDriver::Open - num_potential_inputs %d", num_potential_inputs); jack_info("JackWinMMEDriver::Open - num_potential_outputs %d", num_potential_outputs); + period = 0; + TIMECAPS caps; + if (timeGetDevCaps(&caps, sizeof(TIMECAPS)) != TIMEERR_NOERROR) { + jack_error("JackWinMMEDriver::Open - could not get timer device " + "capabilities. Continuing anyway ..."); + } else { + period = caps.wPeriodMin; + if (timeBeginPeriod(period) != TIMERR_NOERROR) { + jack_error("JackWinMMEDriver::Open - could not set minimum timer " + "resolution. Continuing anyway ..."); + period = 0; + } else { + + jack_info("JackWinMMEDriver::Open - multimedia timer resolution " + "set to %d milliseconds.", period); + + } + } + if (num_potential_inputs) { try { input_ports = new JackWinMMEInputPort *[num_potential_inputs]; } catch (std::exception e) { jack_error("JackWinMMEDriver::Open - while creating input port " "array: %s", e.what()); - return -1; + goto unset_timer_resolution; } for (int i = 0; i < num_potential_inputs; i++) { try { @@ -196,6 +223,13 @@ JackWinMMEDriver::Open(bool capturing, bool playing, int in_channels, return 0; } + if (output_ports) { + for (int i = 0; i < output_count; i++) { + delete output_ports[i]; + } + delete[] output_ports; + output_ports = 0; + } destroy_input_ports: if (input_ports) { for (int i = 0; i < input_count; i++) { @@ -204,6 +238,13 @@ JackWinMMEDriver::Open(bool capturing, bool playing, int in_channels, delete[] input_ports; input_ports = 0; } + unset_timer_resolution: + if (period) { + if (timeEndPeriod(period) != TIMERR_NOERROR) { + jack_error("JackWinMMEDriver::Open - failed to unset timer " + "resolution."); + } + } return -1; } diff --git a/windows/winmme/JackWinMMEDriver.h b/windows/winmme/JackWinMMEDriver.h index 52ae5f2b..3bcdb7e6 100644 --- a/windows/winmme/JackWinMMEDriver.h +++ b/windows/winmme/JackWinMMEDriver.h @@ -33,6 +33,7 @@ namespace Jack { JackWinMMEInputPort **input_ports; JackWinMMEOutputPort **output_ports; + UINT period; public: diff --git a/windows/winmme/JackWinMMEOutputPort.cpp b/windows/winmme/JackWinMMEOutputPort.cpp index dc1f8a70..1a40c9ef 100644 --- a/windows/winmme/JackWinMMEOutputPort.cpp +++ b/windows/winmme/JackWinMMEOutputPort.cpp @@ -130,37 +130,31 @@ bool JackWinMMEOutputPort::Execute() { for (;;) { - if (! Wait(thread_queue_semaphore)) { + if (! Wait(thread_queue_semaphore)) { jack_log("JackWinMMEOutputPort::Execute BREAK"); - + break; } - jack_midi_event_t *event = thread_queue->DequeueEvent(); if (! event) { break; } jack_time_t frame_time = GetTimeFromFrames(event->time); - for (jack_time_t current_time = GetMicroSeconds(); - frame_time > current_time; current_time = GetMicroSeconds()) { - jack_time_t sleep_time = frame_time - current_time; + jack_time_t current_time = GetMicroSeconds(); + if (frame_time > current_time) { + LARGE_INTEGER due_time; - // Windows has a millisecond sleep resolution for its Sleep calls. - // This is unfortunate, as MIDI timing often requires a higher - // resolution. For now, we attempt to compensate by letting an - // event be sent if we're less than 500 microseconds from sending - // the event. We assume that it's better to let an event go out - // 499 microseconds early than let an event go out 501 microseconds - // late. Of course, that's assuming optimal sleep times, which is - // a whole different Windows issue ... - if (sleep_time < 500) { + // 100 ns resolution + due_time.QuadPart = - ((frame_time - current_time) * 10); + if (! SetWaitableTimer(timer, &due_time, 0, NULL, NULL, 0)) { + WriteOSError("JackWinMMEOutputPort::Execute", + "ChangeTimerQueueTimer"); break; } - if (sleep_time < 1000) { - sleep_time = 1000; + if (! Wait(timer)) { + break; } - JackSleep(sleep_time); } jack_midi_data_t *data = event->buffer; DWORD message = 0; @@ -216,6 +210,15 @@ JackWinMMEOutputPort::Execute() return false; } +void +JackWinMMEOutputPort::GetOutErrorString(MMRESULT error, LPTSTR text) +{ + MMRESULT result = midiOutGetErrorText(error, text, MAXERRORLENGTH); + if (result != MMSYSERR_NOERROR) { + snprintf(text, MAXERRORLENGTH, "Unknown MM error code '%d'", error); + } +} + void JackWinMMEOutputPort::HandleMessage(UINT message, DWORD_PTR param1, DWORD_PTR param2) @@ -289,15 +292,23 @@ JackWinMMEOutputPort::Signal(HANDLE semaphore) bool JackWinMMEOutputPort::Start() { - bool result = thread->GetStatus() != JackThread::kIdle; - if (! result) { - result = ! thread->StartSync(); - if (! result) { - jack_error("JackWinMMEOutputPort::Start - failed to start MIDI " - "processing thread."); - } + if (thread->GetStatus() != JackThread::kIdle) { + return true; } - return result; + timer = CreateWaitableTimer(NULL, FALSE, NULL); + if (! timer) { + WriteOSError("JackWinMMEOutputPort::Start", "CreateWaitableTimer"); + return false; + } + if (! thread->StartSync()) { + return true; + } + jack_error("JackWinMMEOutputPort::Start - failed to start MIDI processing " + "thread."); + if (! CloseHandle(timer)) { + WriteOSError("JackWinMMEOutputPort::Start", "CloseHandle"); + } + return false; } bool @@ -326,6 +337,10 @@ JackWinMMEOutputPort::Stop() jack_error("JackWinMMEOutputPort::Stop - could not %s MIDI processing " "thread.", verb); } + if (! CloseHandle(timer)) { + WriteOSError("JackWinMMEOutputPort::Stop", "CloseHandle"); + result = -1; + } return ! result; } @@ -346,15 +361,6 @@ JackWinMMEOutputPort::Wait(HANDLE semaphore) return false; } -void -JackWinMMEOutputPort::GetOutErrorString(MMRESULT error, LPTSTR text) -{ - MMRESULT result = midiOutGetErrorText(error, text, MAXERRORLENGTH); - if (result != MMSYSERR_NOERROR) { - snprintf(text, MAXERRORLENGTH, "Unknown MM error code '%d'", error); - } -} - void JackWinMMEOutputPort::WriteOutError(const char *jack_func, const char *mm_func, MMRESULT result) diff --git a/windows/winmme/JackWinMMEOutputPort.h b/windows/winmme/JackWinMMEOutputPort.h index d43eae35..fc18becf 100644 --- a/windows/winmme/JackWinMMEOutputPort.h +++ b/windows/winmme/JackWinMMEOutputPort.h @@ -36,6 +36,9 @@ namespace Jack { HandleMessageEvent(HMIDIOUT handle, UINT message, DWORD_PTR port, DWORD_PTR param1, DWORD_PTR param2); + void + GetOutErrorString(MMRESULT error, LPTSTR text); + void HandleMessage(UINT message, DWORD_PTR param1, DWORD_PTR param2); @@ -45,19 +48,17 @@ namespace Jack { bool Wait(HANDLE semaphore); + void + WriteOutError(const char *jack_func, const char *mm_func, + MMRESULT result); + HMIDIOUT handle; JackMidiBufferReadQueue *read_queue; HANDLE sysex_semaphore; JackThread *thread; JackMidiAsyncQueue *thread_queue; HANDLE thread_queue_semaphore; - - void - GetOutErrorString(MMRESULT error, LPTSTR text); - - void - WriteOutError(const char *jack_func, const char *mm_func, - MMRESULT result); + HANDLE timer; public: