Reputation: 1015
I'm working on a proof of concept for AudioKit
to evaluate if it fits our needs.
We are currently using AVAudioRecorder
, AVPlayer
and AVMutableComposition
for a multi-track recorder and it works great, but I want to try to do the same with AudioKit
or AVAudioEngine
.
I'm new with AudioKit
so I need some guidance:
I'm using AKClipRecorder
as the recorder and calling start(at:)
to sync with the players, but I think something is not set properly, because there is always a delay in the recording. Why could this be happening? For the players, I'm using an array of AKPlayers
and calling play(at:)
to sync them, and from my tests, the players seem to sync ok.
I'm having a hard time to find how to sync AKMetronome
with the recorder and players, there is no start(at:)
API. How can I achieve this? And is it possible to start the recorder and players in a certain beat?
Lastly, is there a way to set the file format of the AKClipRecorder
?
I add my code below.
import Foundation
import AudioKit
class MultiTrackRecorder {
// MARK: - Constants & Vars
private var mic: AKMicrophone!
private var micBooster: AKBooster!
private var mainMixer: AKMixer!
private var recorder: AKClipRecorder!
private var players: [AKPlayer] = []
private var metronome: AKMetronome!
// MARK: - Public Methods
func startEngine() throws {
AKSettings.bufferLength = .medium
AKSettings.defaultToSpeaker = true
try AKSettings.setSession(category: .playAndRecord)
mic = AKMicrophone()
recorder = AKClipRecorder(node: mic)
micBooster = AKBooster(mic)
micBooster.gain = 0
metronome = AKMetronome()
metronome.tempo = 120
mainMixer = AKMixer()
mainMixer.connect(input: micBooster)
mainMixer.connect(input: metronome)
AudioKit.output = mainMixer
try AudioKit.start()
}
func startRecording() throws {
try recorder.recordClip(completion: recordingEnded)
let startTime = AVAudioTime.now() + 0.25
recorder.start(at: startTime)
players.forEach { $0.play(at: startTime) }
//metronome.start()
}
func stopRecording() {
recorder.stopRecording()
recorder.stop()
players.forEach { $0.stop() }
metronome.stop()
}
func play() {
let startTime = AVAudioTime.now() + 0.25
players.forEach { $0.play(at: startTime) }
}
func stop() {
players.forEach { $0.stop() }
}
// MARK: - Private Methods
private func addPlayer(withFileURL url: URL) {
guard let player = AKPlayer(url: url) else { return }
players.append(player)
mainMixer.connect(input: player)
}
private func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
private func getDocsPathWithRandomFileName() -> URL {
let docsDirectory = getDocumentsDirectory()
let fileName = "\(UUID().uuidString).caf"
return docsDirectory.appendingPathComponent(fileName, isDirectory: false)
}
private func recordingEnded(url: URL?, somethimg: Double, error: Error?) {
guard let savedFile = url else {
print("TODO: Handle this error")
return
}
do {
let moveToDirectory = getDocsPathWithRandomFileName()
try FileManager.default.moveItem(at: savedFile, to: moveToDirectory)
addPlayer(withFileURL: moveToDirectory)
print("New track has been saved in: \(moveToDirectory.absoluteString)")
} catch {
print("TODO: Handle this error")
}
}
}
Upvotes: 2
Views: 1015
Reputation: 4955
Try AKSamplerMetronome instead of AKMetronome, it has a play(at:) function.
As for getting the timing right, there are a few things at play. AKMicrophone won't provide correct timestamps, use AudioKit.engine.inputNode instead. If you really want it to be accurate, you will need to compensate for io latency.
let startTime = AVAudioTime.now() + 0.25
let audioSession = AVAudioSession.sharedInstance()
// start players in advance to compensate for hardware output latency
let playbackStart = startTime - audioSession.outputLatency
//start recorders late to compensate for hardware input latency.
let recordingStart = startTime + audioSession.inputLatency
I just posted an example where loopback audio is compared for accuracy in the repo yesterday.
Upvotes: 3