swiftaudiokitaksequencer

AudioKit: How to change tempo on the fly?


I am new to AudioKit and programming music app. I'm building a metronome app and using AudioKit's AKMetronome. I want to have a feature where a user can specify a sequence of beat patterns with different tempo. But I find it is inaccurate to use apple's DispatchQueue.

I'm thinking of rewriting the metronome using AKSequencer. Is there a way to use AudioKit's sequencer to change tempo on the fly or generate a sequence with multiple different tempo? (Sequencer example: https://github.com/AudioKit/Cookbook/blob/main/Cookbook/Cookbook/Recipes/Shaker.swift)

metronome.tempo = 120
let first_interval = 60.0 / 120.0
let switchTime1 = DispatchTime.now() + (first_interval * 4.0)
metronome.play()

DispatchQueue.main.asyncAfter(deadline: switchTime1, execute: {
    self.metronome.tempo = 200
})

let second_inter = 60.0 / 200.0
let switchTime2 = switchTime1 + (second_inter * 8.0)

DispatchQueue.main.asyncAfter(deadline: switchTime2, execute: {
    self.metronome.tempo = 120
})

Update:

I figured out that you can assign a callback function to AKMetronome using AKMetronome.callback. (https://audiokit.io/docs/Classes/AKMetronome.html#/s:8AudioKit11AKMetronomeC8callbackyycvp) You can then update the tempo at the start of a new sequence.


Solution

  • A possible solution would be to create a tempo track, which would contain tempo events that when processed change the sequencer's tempo.

    This is an outline of what should be done:

    1. Create a track to contain the tempo events, using AKSequencer's addTrack method. Connect this track to an AKCallbackInstrument. Please see this answer on how to connect an AKCallbackInstrument to an AKSequencer track.
    2. Add the tempo events to the track, at the time positions where there are tempo changes. As far as I know, there are no standard MIDI events for indicating tempo changes (such as a control change for tempo). But as you will be interpreting the events yourself with a callback function, it doesn't really matter what type of event you use. I explain below how to represent the tempo.
    3. Process the events in the callback function and set AKSequencer's tempo to the indicated tempo.

    It’s a little difficult to represent the tempo value inside a MIDI event because usually, MIDI parameters go from 0 to 127. What I would do is use a Note On event, in the note's pitch I would store tempo div 128 and in the note's velocity, tempo % 128.

    This is what your callback function would look like:

    func tempoCallback(status:UInt8, note:MIDINoteNumber, vel:MIDIVelocity) -> () {
        guard let status = AKMIDIStatus(byte: status),
            let type = status.type,
            type == .noteOn else { return }
        let tempo: Int = note * 128 + vel
        sequencer.tempo = Double(tempo)
    }