In v4, If you use AKPlayer
with looping and use setPosition
API, the player loops between the position set with setPosition
and the players total duration. What I needed was to start from the position set by setPosition
API and loop to the beginning when looping occurs. I am not sure if this is intentional or not but I need to be able to seek to a certain point in the Player and loop to the beginning and for now it seems that this is not a feature in AudioKit.
Is there a way or workaround to achieve this? I need to accomplish this as a requirement.
To reproduce here is an full playground example:
import AudioKitPlaygrounds
import AudioKit
let file = try AKAudioFile(readFileName: "3.aac")
//: Set up a player to the loop the file's playback
var player = AKPlayer(audioFile: file)
player.isLooping = true
player.buffering = .always
AKManager.output = player
try AKManager.start()
player.setPosition(player.duration - 2)
player.play(at: AVAudioTime.now())
// player loops between player.duration - 2, player.duration forever. I want it to loop between 0, player.duration
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
Expected behavior I expected it to loop back to the beginning.
Details:
If you intend to develop some kind of looper application, I think the approach is to use a sequencer to coordinate the playback of your audio files. At each position where a sound should start playing, you add an event to the sequence.
The sequencer can be connected to a callback instrument, which is a node that passes the events to an user-defined function. In your callback function, you would start playback of the audio file indicated by that event.
This is an outline of what should be done:
length
to the total duration of the song (notice these properties are represented in beats instead of seconds). Set the sequencer's loopEnabled
as desired.Note On
.You would need a couple of classes to represent some basic information about where each sample occurs in the song:
class Beat {
var sample: Sample!
var onsetTime: Double
var endTime {
get {
return onsetTime + sample.duration
}
}
}
class Sample {
var url: URL!
var duration: Double
}
class ViewController: UIViewController {
var samples: [Sample] = []
var beats: [Beat] = []
var player: AKPlayer!
var sequencer: AKSequencer!
}
On the note’s pitch you would store the index of the sample you want to play. This is what your callback function would look like:
func playCallback(status:UInt8, note:MIDINoteNumber, vel:MIDIVelocity) -> () {
guard let status = AKMIDIStatus(byte: status),
let type = status.type,
type == .noteOn else { return }
DispatchQueue.main.async {
player.load(samples[note].url)
player.play()
}
}
You can use AKSequencer
’s seek
method to start playing your song at some arbitrary position.
There is a special case where the sequencer starts to play in the middle of a beat. In that case, you must manually start the playback of that sample.
func play(at time: Double = 0) {
sequencer.seek(time)
sequencer.play()
for beat in beats {
if beat.onsetTime < time && time < beat.endTime {
player.load(beat.sample.url)
player.play(from: time - beat.onsetTime)
}
}
}