You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
55 lines
2.1 KiB
55 lines
2.1 KiB
import CAudioRing
|
|
import Foundation
|
|
|
|
/// Producer side of one route: takes audio from any thread (NDI, input device) into a stereo ring buffer.
|
|
///
|
|
/// One feed has exactly one producer and one consumer (the route on the render thread).
|
|
public final class AudioFeed: @unchecked Sendable {
|
|
let ring: OpaquePointer
|
|
/// Sample rate of the audio in the ring. Written by the producer, read by the render thread.
|
|
private(set) var sampleRate: Double = 0
|
|
// Producer-only scratch for interleaving.
|
|
private var scratch: UnsafeMutablePointer<Float>
|
|
private var scratchFrames = 0
|
|
|
|
public init(capacitySeconds: Double = 1, maxSampleRate: Double = 96_000) {
|
|
ring = musubi_ring_create(UInt32(capacitySeconds * maxSampleRate), 2)!
|
|
scratchFrames = 4096
|
|
scratch = .allocate(capacity: scratchFrames * 2)
|
|
}
|
|
|
|
deinit {
|
|
musubi_ring_destroy(ring)
|
|
scratch.deallocate()
|
|
}
|
|
|
|
/// Pushes planar audio. Mono is duplicated to both channels; channels beyond two are ignored.
|
|
public func push(planar data: UnsafePointer<Float>, channelStride: Int, channels: Int, frames: Int, sampleRate: Double) {
|
|
guard channels > 0, frames > 0 else { return }
|
|
self.sampleRate = sampleRate
|
|
ensureScratch(frames)
|
|
let left = data
|
|
let right = channels > 1 ? data + channelStride : data
|
|
for i in 0..<frames {
|
|
scratch[i * 2] = left[i]
|
|
scratch[i * 2 + 1] = right[i]
|
|
}
|
|
musubi_ring_write(ring, scratch, UInt32(frames))
|
|
}
|
|
|
|
/// Consumer side for taps: reads up to `frames` interleaved stereo frames; returns the number read.
|
|
public func read(into interleaved: UnsafeMutablePointer<Float>, frames: Int) -> Int {
|
|
Int(musubi_ring_read(ring, interleaved, UInt32(frames)))
|
|
}
|
|
|
|
/// Sample rate of the most recent push (0 before the first).
|
|
public var currentSampleRate: Double { sampleRate }
|
|
|
|
private func ensureScratch(_ frames: Int) {
|
|
guard frames > scratchFrames else { return }
|
|
scratch.deallocate()
|
|
scratchFrames = frames
|
|
scratch = .allocate(capacity: frames * 2)
|
|
}
|
|
}
|