首先,我调用AKMIDISampler播放音频文件,然后将其分配给AKSequencer。我使用的“ midi”文件只有2小节长,C3音符,单音轨midi文件,与我要播放的音频文件的长度完全一样。但是,在调用AKAudioFile时,我想随机选择mp3文件。我暂时将1.mp3、2.mp3和3.mp3制作如下。
let track = AKMIDISampler()
let sequencer = AKSequencer(filename: "midi")
try? track.loadAudioFile(AKAudioFile(readFileName: String(arc4random_uniform(3)+1) + ".mp3"))
sequencer.tracks[0].setMIDIOutput(track.midiIn)
// Tempo track I had to made to remove sine wave
sequencer.tracks[1].setMIDIOutput(track.midiIn)
做了一些音序器设置,
sequencer.setTempo(128.0)
sequencer.setLength(AKDuration(beats: 8))
sequencer.setLoopInfo(AKDuration(beats: 8), numberOfLoops: 4)
sequencer.preroll()
并将AKMIDISampler分配给AudioKit.output,然后执行sequencer.play()。
音序器播放成功!它随机加载了三个mp3文件,并播放了8拍(2小节),准确地循环了4次。
但是我的目标是每次循环重复时都随机加载MP3文件。似乎定序器在循环时仅播放第一个分配的mp3文件。我正在努力寻找解决方案。
也许我可以使用“ AKCallbackInstrument”?由于在这种情况下我是通过Midi音符播放音频文件的,因此无论何时关闭Midi音符,我都可以重置“ loadAudioFile”?这样,我可能会循环播放音序器,并在每个循环中随机播放一个音频文件。这只是一个想法,但对我而言,现在很难正确编写它。我希望我走在正确的轨道上。如果能在这里获得建议,那就太好了。 <3
答案 0 :(得分:2)
您肯定在正确的轨道上-使用AKSequencer + AKCallbackInstrument可以轻松地使随机音频文件以固定的间隔循环播放。但是我不必担心尝试重新加载NoteOff消息。
我首先将每个mp3加载到数组中的单独播放器(例如AKAppleSampler)中(例如,您可以将其命名为players
),然后创建一个方法来随机触发其中一个播放器:>
func playRandom() {
let playerIndex = Int(arc4random_uniform(UInt32(players.count)))
try? players[playerIndex].play()
}
创建音序器时,添加一个音轨并将其分配给AKCallbackInstrument
。此AKCallbackInstrument
的回调函数将在收到noteOn消息时调用playRandom
。
seq = AKSequencer()
track = seq.newTrack()!
callbackInst = AKCallbackInstrument()
track.setMIDIOutput(callbackInst.midiIn)
callbackInst.callback = { status, note, vel in
guard status == .noteOn else { return }
self.playRandom()
}
不需要用MIDI文件加载音序器。您可以将触发MIDI事件直接添加到音轨中。
track.add(noteNumber: 48, // i.e., C3
velocity: 127,
position: AKDuration(beats: 0), // noteOn message here
duration: AKDuration(beats: 8), // noteOff 8 beats later
channel: 0)
您的正弦波问题可能是由您在MIDI文件中创建的额外音轨(可能是速度音轨)引起的,该音轨尚未分配输出。您可以通过直接添加MIDI事件来完全避免该问题。
原则上,您可以使用回调检查noteOff事件并从noteOff触发代码,但是在您的情况下,我不建议这样做。没有充分的理由将单个播放器重新用于多个音频文件。加载文件是您最有可能产生错误的地方。如果您的文件还没有播放完,而您尝试加载另一个文件,会发生什么呢?将多个播放器保留在内存中所需的资源是微不足道的-如果要多次播放同一文件,则一次加载该文件并将播放器保留在内存中会更安全,更安全。
答案 1 :(得分:1)
这很有帮助,c_booth!多亏了您,我今天取得了长足的进步。这是我根据您的建议写的。首先,我制作了一个包含6个mp3文件的AKPlayers数组。将它们分配给AKMixer,然后我将其称为音序器和回调工具。我在音序器上做了一个音轨和一个音符,在每个noteOn上调用了“ playRandom”功能:
let players: [AKPlayer] = {
do {
let filenames = ["a1.mp3", "a2.mp3", "a3.mp3", "b1.mp3", "b2.mp3", "b3.mp3"]
return try filenames.map { AKPlayer(audioFile: try AKAudioFile(readFileName: $0)) }
} catch {
fatalError()
}
}()
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()
}
}
func sequencerSettings() {
sequencer.setTempo(128.0)
sequencer.setLength(AKDuration(beats: 16))
sequencer.setLoopInfo(AKDuration(beats: 16), numberOfLoops: 4)
sequencer.preroll()
}
func makeConnections() {
players.forEach { $0 >>> mixer }
}
func startAudioEngine() {
AudioKit.output = mixer
do {
try AudioKit.start()
} catch {
print(error)
fatalError()
}
}
func startSequencer() {
sequencer.play()
}
效果很好。它从6个mp3文件中随机选择一个(它们的长度,128bpm和16个节拍都相同)。不过,我发现奇怪的是,第一次播放一次播放了两个音频文件。在第二个循环后,它工作正常。我更改了numberOfLoop设置,enableLooping()等,但仍然相同-第一次播放时播放两个文件。跟踪计数仍为1,正如您所见,我只调用了一个AKPlayer。我对此有什么办法?
此外,最终,我想在阵列上调用数百个mp3文件,因为我要制作的是一种DJing应用程序(类似于Ableton Live预设)。您是否认为使用AKPlayer是个好主意,假设此代码将从云中加载mp3文件并将其流式传输给用户?非常感激。 <3