1

In an iOS app (Swift), how would I schedule audio samples to playback at exact times?

I'd like to make music by scheduling audio samples to play at exact times - a series of "tracks". I have some idea that I should be using AVFoundation, but I find the documentation lacking in serving this particular use-case.

I undersand that AudioKit exists, but I am looking to eventually move my app to the Apple Watch, which is unsupported by AudioKit at this time.

John McDowall
  • 435
  • 3
  • 10

1 Answers1

2

I worked out what I needed to do.

Make an AVMutableComposition, add a mutable track, and on that track add segments (silence and a view on an sound asset)

import AVFoundation

let drumUrl = Bundle.main.url(forResource: "bd_909dwsd", withExtension: "wav")!

func makePlayer(bpm: Float, beatCount: Int) -> AVPlayer? {
    let beatDuration = CMTime(seconds: Double(60 / bpm), preferredTimescale: .max)
    
    let composition = AVMutableComposition()
    guard let track = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) else {
        return nil
    }
    
    let drumAsset = AVAsset(url: drumUrl)
    let drumDuration = drumAsset.duration
    let drumTimeRange = CMTimeRange(start: CMTime.zero, duration: drumDuration)
    
    let silenceDuration = beatDuration - drumDuration
    
    var prevBeatEnd = CMTime.zero
    
    for deatIndex in 0 ..< beatCount {
        let drumTargetRange = CMTimeRange(start: prevBeatEnd, duration: drumDuration)
        let drumSegment = AVCompositionTrackSegment(url: drumUrl, trackID: track.trackID, sourceTimeRange: drumTimeRange, targetTimeRange: drumTargetRange)
        track.segments.append(drumSegment)
        
        if deatIndex == 0 {
            prevBeatEnd = prevBeatEnd + drumDuration
        } else {
            let silenceTargetRange = CMTimeRange(start: prevBeatEnd, duration: silenceDuration)
            track.insertEmptyTimeRange(silenceTargetRange)
            prevBeatEnd = prevBeatEnd + silenceDuration + drumDuration
        }
    }
    
    try! track.validateSegments(track.segments)
    
    let playerItem = AVPlayerItem(asset: composition)
    
    return AVPlayer(playerItem: playerItem)
}
John McDowall
  • 435
  • 3
  • 10