From 7dc3a0fc8ca07808f3094599c2bad22c192173a8 Mon Sep 17 00:00:00 2001 From: Devin Anderson Date: Tue, 19 Apr 2011 12:04:07 -0700 Subject: [PATCH] WinMME: Use waitable timers, which have a resolution of 100 ns. Use timeBeginPeriod() and timeEndPeriod() for greater multimedia timer resolution. --- windows/winmme/JackWinMMEDriver.cpp | 35 +++++++++++- windows/winmme/JackWinMMEDriver.h | 1 + windows/winmme/JackWinMMEOutputPort.cpp | 76 +++++++++++++------------ windows/winmme/JackWinMMEOutputPort.h | 15 ++--- 4 files changed, 84 insertions(+), 43 deletions(-) diff --git a/windows/winmme/JackWinMMEDriver.cpp b/windows/winmme/JackWinMMEDriver.cpp index f7793363..3f949242 100644 --- a/windows/winmme/JackWinMMEDriver.cpp +++ b/windows/winmme/JackWinMMEDriver.cpp @@ -119,6 +119,13 @@ JackWinMMEDriver::Close() delete[] output_ports; output_ports = 0; } + if (period != 1000) { + if (timeEndPeriod(period) != TIMERR_NOERROR) { + jack_error("JackWinMMEDriver::Close - failed to unset timer " + "resolution."); + result = -1; + } + } return result; } @@ -139,13 +146,25 @@ 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); + // Get the best minimum timer resolution possible. + for (period = 1; i < 1000; i++) { + if (timeBeginPeriod(period) == TIMERR_NOERROR) { + jack_info("JackWinMMEDriver::Open - timer resolution set to %d " + "milliseconds.", period); + goto open_inputs; + } + } + jack_error("JackWinMMEDriver::Open - could not set any timer resolution. " + "Continuing anyway ..."); + + open_inputs: 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 +215,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 +230,13 @@ JackWinMMEDriver::Open(bool capturing, bool playing, int in_channels, delete[] input_ports; input_ports = 0; } + unset_timer_resolution: + if (period != 1000) { + 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: