-1

I've been looking into how to implement a Metronome countdown with the AudioKit lib but ran into an issue related to threads and my implementation.

My implementation uses the AudioKit AKMetronome, a method to handle the metronome.start and the metronome.callback handler to start recording after a given number of bars are completed.

init () {
  metronome.callback = handler
}

func record () {
    self.metronome.start()
}

The handler computes the metronome position in time and if the desired number of bars completes (countdown), the recorder starts.

Unfortunately, running the .record of AKNodeRecorder in the .callback handler of AKMetronome causes the warning message:

Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.

For that reason, the call to start recording in the metronome.callback handler is passed to the main thread through the GCD API:

            DispatchQueue.main.sync {
                do {
                    try self.recorder.record()
                } catch {
                    print("[ERROR] Oops! Failed to record...")
                }
            }

I've used the .sync blocking method to resolve the computation immediately as possible since timing is critical in audio applications (the call should be executed in a real-time thread, ideally). Is my understanding that the GCP API main thread provides the highest priority, but I'm not certain if that's the best option for a time-sensitive application?

halfer
  • 19,824
  • 17
  • 99
  • 186
punkbit
  • 7,347
  • 10
  • 55
  • 89

2 Answers2

1

OK, so the actual question has nothing to do with metronomes or countdowns? What you really want to know is: will using sync from a background thread get me onto the main thread faster?

If so: Basically no. This is not what sync is for or what it means. The async/sync difference has absolutely no effect on speed. If you get onto the main thread using async, you will get on as soon it is free, so you gain nothing whatever by saying sync.

Moreover, if you already arrived into the callback in an async background thread, any damage that can be done is already done; your timing is now inexact and there is absolutely nothing you can do about it unless you happen to have a time machine in your pocket.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Thanks for looking @matt! The context `audio app` is important because of timing, what I take from your answer is that I should not execute the recorder in the metronome `.callback` since that's already executed in a background thread; I'll move the recorder call out of the `.callback` and execute it along the metronome start and use the `Timer`, but read before that `Timer.scheduledTimer` should not be used because it's not reliable for audio applications. Clearly the fact I'm working in a "timing sensitive app" is important, so hope it's ok to keep it there in the topic :) – punkbit May 03 '20 at 20:11
  • Well I don't know if the `.callback` is done in a background thread, but that is what the error message seems to imply, eh. – matt May 03 '20 at 23:04
  • Yes, I have assumed the same based in the message. – punkbit May 03 '20 at 23:11
0

Following up on @matt advice regarding calls inside callbacks, the approach I took change based in a .scheduledTimer of Timer.

func record () {
    metronome.restart()
    Timer.scheduledTimer(withTimeInterval: self.barLength, repeats: false) { _ in
        self.startRecording()
        print("Timer fired!")
    }
}

The record is a handler that initiates the AKMetronome, opted to use .restart so that every time's requested starts from the very start.

A property .barLength holds the value for the computed length of the desired countdown. The .startRecording method that is called, is a handler that takes care of AKRecorder .record call.

Future reader, have in mind that Timer is not meant to be accurate according to (Accuracy of NSTimer) but unfortunately, this is the best approach I've tested and I found so far.

punkbit
  • 7,347
  • 10
  • 55
  • 89