Combining Actors and AVAudioSourceNode

I'm currently trying to get into audio programming and have a very simple synthesizer setup, that uses AVAudioSourceNode for creating simple sounds.
My Synth class, where the sound generation happens, is structured like this:

class Synth {
    private let audioEngine: AVAudioEngine
    private let sampleRate: Double

    private(set) var phase: Float = 0

    private var sampleSource: SampleSource

    private lazy var sourceNode = AVAudioSourceNode.init { (_, _, frameCount, audioBufferList) -> OSStatus in
        let ablPointer = UnsafeMutableAudioBufferListPointer(audioBufferList)
        for frame in 0..<Int(frameCount) {
            let sampleVal = self.sampleSource.sample(at: &self.phase, withSampleRate: Float(self.sampleRate))
            for buffer in ablPointer {
                let buf = UnsafeMutableBufferPointer<Float>(buffer)
                buf[frame] = sampleVal

        return noErr

    init(withSampleSource sampleSource: SampleSource = Oscillator(withWaveform: .sine, frequency: 440)) {
        self.audioEngine = AVAudioEngine()

        let mainMixer = self.audioEngine.mainMixerNode
        let outputNode = self.audioEngine.outputNode
        let format = outputNode.inputFormat(forBus: 0)

        self.sampleRate = format.sampleRate

        self.sampleSource = sampleSource

        let inputFormat = AVAudioFormat(commonFormat: format.commonFormat, sampleRate: self.sampleRate, channels: 1, interleaved: format.isInterleaved)
        self.audioEngine.connect(self.sourceNode, to: mainMixer, format: inputFormat)
        self.audioEngine.connect(mainMixer, to: outputNode, format: nil)
        mainMixer.outputVolume = 0.5

    public func setSampleSource(to sampleSource: SampleSource) {
        self.sampleSource = sampleSource

Of course I only included the most important bits, in the real class there is a lot more going on. That brings me to my question: AFAIUI, this class is not thread-safe, as the AVAudioSourceNode closure uses e.g. the sampleSource variable, which could change at any time when setSampleSource(to:) is called, probably even from another thread. How should I model this to be thread-safe? Is using an actor from the new concurrency features in Swift 5.5 a useful way? Would it have to run on the same thread as the closure to actually be useful? Maybe someone can clarify these questions for me, as I always have a hard time to wrap my head around multithreaded programming.

Terms of Service

Privacy Policy

Cookie Policy