How to achieve a very accurate time in Swift?

I am working on a music application with an arpeggio / sequencing function that requires a lot of time accuracy. Currently, using the "Timer", I have achieved accuracy with an average jitter of ~ 5 ms, but a maximum jitter of ~ 11 ms, which is unacceptable for fast arpeggios of the 8th, 16th notes and 32nd notes especially.

I read that "CADisplayLink" is more accurate than the "Timer", but since it is limited to 1/60 of a second for its accuracy (~ 16-17 ms), it seems that this would be less accurate than what I achieved with Timer.

Will diving into CoreAudio be the only way to achieve what I want? Is there any other way to achieve a more accurate time?

+4
source share
2 answers

I did some testing Timerand DispatchSourceTimer(like a GCD timer) on the iPhone 7 with 1000 data points at 0.05 second intervals. I expected the GCD timer to be significantly more accurate (given that it had a dedicated queue), but I found that they were comparable to the standard deviation of my various tests in the range of 0.2-0.8 milliseconds and the maximum deviation from the average of about 2 - 8 milliseconds.

mach_wait_until, TN2169: iOS/OS X, , 4 , , Timer GCD.

, , mach_wait_until - , thread_policy_set . , , , Swift? TN2169:

var timebaseInfo = mach_timebase_info_data_t()

func configureThread() {
    mach_timebase_info(&timebaseInfo)
    let clock2abs = Double(timebaseInfo.denom) / Double(timebaseInfo.numer) * Double(NSEC_PER_SEC)

    let period      = UInt32(0.00 * clock2abs)
    let computation = UInt32(0.03 * clock2abs) // 30 ms of work
    let constraint  = UInt32(0.05 * clock2abs)

    let THREAD_TIME_CONSTRAINT_POLICY_COUNT = mach_msg_type_number_t(MemoryLayout<thread_time_constraint_policy>.size / MemoryLayout<integer_t>.size)

    var policy = thread_time_constraint_policy()
    var ret: Int32
    let thread: thread_port_t = pthread_mach_thread_np(pthread_self())

    policy.period = period
    policy.computation = computation
    policy.constraint = constraint
    policy.preemptible = 0

    ret = withUnsafeMutablePointer(to: &policy) {
        $0.withMemoryRebound(to: integer_t.self, capacity: Int(THREAD_TIME_CONSTRAINT_POLICY_COUNT)) {
            thread_policy_set(thread, UInt32(THREAD_TIME_CONSTRAINT_POLICY), $0, THREAD_TIME_CONSTRAINT_POLICY_COUNT)
        }
    }

    if ret != KERN_SUCCESS {
        mach_error("thread_policy_set:", ret)
        exit(1)
    }
}

:

private func nanosToAbs(_ nanos: UInt64) -> UInt64 {
    return nanos * UInt64(timebaseInfo.denom) / UInt64(timebaseInfo.numer)
}

private func startMachTimer() {
    Thread.detachNewThread {
        autoreleasepool {
            self.configureThread()

            var when = mach_absolute_time()
            for _ in 0 ..< maxCount {
                when += self.nanosToAbs(UInt64(0.05 * Double(NSEC_PER_SEC)))
                mach_wait_until(when)

                // do something
            }
        }
    }
}

. , , when ( , , ), , , .

, mach_wait_until, , Timer GCD, / , do dont , ?

, , CoreAudio , .

+3

Core Audio AVFoundation.

+2

Source: https://habr.com/ru/post/1681518/


All Articles