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

时间:2018-08-27 07:17:52

标签: audiokit

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

2 个答案:

答案 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