This is a multiple audiofile playback project I am currently working on, where multiple AKPlayers are played in a random order, through AKSequencer.
First, I have an array of filenames:
Let filenames = [“1.mp3”, “2.mp3, “3.mp3”, … “10.mp3”]
This is loaded on AKPlayer individually, to later call it in a random order:
Let players: [AKPlayer] = {
Do {
Let filenames = [“1.mp3”, “2.mp3, “3.mp3”, … “10.mp3”]
Return try filenames.map { AKPlayer(audioFile: try AKAudioFile(readRileName: $0)) }
} catch {
fatalError()
}
}()
Then, I called AKPlayer through AKsequencer, by triggering it through ‘playRandom’ function:
Let sequencer = AKSequencer()
Let callbackInst = AKCallbackInstrument()
func playRandom() {
let playerIndex = Int(arc4random_uniform(UInt32(players.count)))
players[playerIndex].play()
}
func addTracks() {
let track = sequencer.newTrack()!
track.add(noteNumber: 48, velocity: 127, position: AKDuration(beats: 0), duration: AKDuration(beats: 16), channel: 0)
track.setMIDIOutput(callbackInst.midiIn)
callbackInst.callback = { status, note, vel in
guard status == .noteOn else { return }
self.playRandom()
}
}
Lastly, I set AKPlayer as AudioKit.output, and started the sequencer. So far this was successful! The sequencer plays AKPlayer seamlessly, in a random order.
But I wanted to try different kind of randomness: repeating randomly selected player 2 or 3 times. (like 2, 2, 3, 3, 3, 1, 1, 5, 5, 5, 9, 9, …) Right now, ‘playRandom’ simply chooses different AKPlayer on each repeat.
As one solution, thanks to StackOverFlow masters, I tried something like:
class RandomWithRepeats {
var range: ClosedRange<Int>
var repeatRange: ClosedRange<Int>
var repeatCount = 0
var value = 0
init(range: ClosedRange<Int>, repeatRange: ClosedRange<Int>) {
self.range = range
self.repeatRange = repeatRange
}
// generate a random number in a range
// Just use Int.random(in:) with Swift 4.2 and later
func random(in range: ClosedRange<Int>) -> Int {
return Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound + 1))) + range.lowerBound
}
func nextValue() -> Int {
// if repeatCount is 0, its time to generate a new value and
// a new repeatCount
if repeatCount == 0 {
// For Swift 4.2, just use Int.random(in:) instead
value = self.random(in: range)
repeatCount = self.random(in: repeatRange)
}
repeatCount -= 1
return value
}
}
And then I modified playRandom function like:
func playRandom() {
Let rand = randomWithRepeats(range: 1…players.count, repeatRange: 2…3)
Do { players[rand.nextValue()].play() }
}
Turns out, this is the exact same result because the playRandom function itself triggered (by AKSequencer) each repeat, so it doesn’t actually ‘repeat’ random AKPlayer 2 or 3 times. Can I solve this issue in a different way? Much appreciated. <3
The job of the
RandomWithRepeatsclass is to keep track of when to deliver you a differentAKPlayerinstance, but you are creating a new instance ofRandomWithRepeatseach time you callplayRandom(), so it can't keep track of anything.Create a single instance of
RandomWithRepeatswhen you create the array of players and use this instance in yourplayRandom()method.For a cleaner solution, you might encapsulate the logic of
RandomWithRepeatstogether with theAKPlayerarray into a single class, such as aRandomlyRepeatingPlayerclass.