使用 AKCallbackInstrument.noteOff 在 AKSequencer 中重新加载 AKAudioFile?

Reloading AKAudioFile in AKSequencer using AKCallbackInstrument.noteOff?

首先,我调用了一个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

您绝对是在正确的轨道上 - 您可以使用 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 的触发代码,但我不建议您这样做。没有充分的理由为多个音频文件重复使用单个播放器。加载文件是最有可能产生错误的地方。如果您的文件尚未播放完毕而您尝试加载另一个文件,会发生什么情况?将多个播放器保存在内存中所需的资源非常微不足道 - 如果您要多次播放同一个文件,加载一次并将播放器保存在内存中会更干净、更安全。

非常有帮助,c_booth!多亏了你,我今天取得了巨大的进步。这是我根据您的建议写的。首先,我制作了一个包含 6 个 mp3 文件的 AKPlayer 数组。它们被分配给 AKMixer,然后我调用了音序器和回调乐器。我在音序器上做了一个音轨和一个音符,它在每个音符上调用 '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: [=10=])) }
    } 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 { [=10=] >>> mixer }
}

func startAudioEngine() {
    AudioKit.output = mixer
    do {
        try AudioKit.start()
    } catch {
        print(error)
        fatalError()
    }
}

func startSequencer() {
    sequencer.play()
}

效果很好。它从 6 个 mp3 文件中随机选择一个(它们都是相同的长度,128bpm 和 16 节拍)。不过,我在这里发现奇怪的是,第一次播放会同时播放两个音频文件。它在第二个循环后工作正常。我更改了 numberOfLoop 设置、enableLooping() 等,但仍然相同 - 在第一次播放时播放两个文件。 trackcount 仍然是 1,如您所见,我只调用了一个 AKPlayer。我能做些什么吗?

此外,最终,我想调用阵列上的数百个 mp3 文件,因为我想要制作的是一种 DJ 应用程序(类似于 Ableton Live 预设)。你认为使用 AKPlayer 是个好主意吗,假设这段代码将从云端加载 mp3 文件并将其流式传输给用户?非常感激。 <3