How do I achieve very accurate timing in Swift?

大城市里の小女人 提交于 2019-12-23 07:59:52

问题


I am working on a musical app with an arpeggio/sequencing feature that requires great timing accuracy. Currently, using `Timer' I have achieved an accuracy with an average jitter of ~5ms, but a max jitter of ~11ms, which is unacceptable for fast arpeggios of 8th, 16th notes & 32nd notes especially.

I've read the 'CADisplayLink' is more accurate than 'Timer', but since it is limited to 1/60th of a second for it's accuracy (~16-17ms), it seems like it would be a less accurate approach than what I've achieved with Timer.

Would diving into CoreAudio be the only way to achieve what I want? Is there some other way to achieve more accurate timing?


回答1:


For acceptable musically accurate rhythms, the only suitable timing source is using Core Audio or AVFoundation.




回答2:


I did some testing of Timer and DispatchSourceTimer (aka GCD timer) on iPhone 7 with 1000 data points with an interval of 0.05 seconds. I was expecting GCD timer to be appreciably more accurate (given that it had a dedicated queue), but I found that they were comparable, with standard deviation of my various trials ranging from 0.2-0.8 milliseconds and maximum deviation from the mean of about 2-8 milliseconds.

When trying mach_wait_until as outlined in Technical Note TN2169: High Precision Timers in iOS / OS X, I achieved a timer that was roughly 4 times as accurate than what I achieved with either Timer or GCD timers.

Having said that, I'm not entirely confident of the mach_wait_until is the best approach, as the determination of the specific policy values for thread_policy_set seem to be poorly documented. But the code below reflects the values I used in my tests, using code adapted from How to set realtime thread in Swift? and 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)
    }
}

I then could do:

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
            }
        }
    }
}

Note, you might want to see if when hasn't already passed (you want to make sure that your timers don't get backlogged if your processing can't be completed in the allotted time), but hopefully this illustrates the idea.

Anyway, with mach_wait_until, I achieved greater fidelity than Timer or GCD timers, at the cost of CPU/power consumption as described in What are the do's and dont's of code running with high precision timers?

I appreciate your skepticism on this final point, but I suspect it would be prudent to dive into CoreAudio and see if it might offer a more robust solution.




回答3:


I'm working on a sequencer App myself, and I would defiantly recommend using AudioKit for those purposes. It has a its own sequencer class. https://audiokit.io/



来源:https://stackoverflow.com/questions/45120492/how-do-i-achieve-very-accurate-timing-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!