Audiokit: Using the New Aksequencer with Any Variety of the Callback Instruments

AudioKit: Using the new AKSequencer with any variety of the callback instruments

To get AKCallbackInstrument working with the new AKSequencer, try connecting your callback instrument to your output, e.g.,

metroCallback >>> mixer

Not obvious, but has worked for me.

Edit: including a minimal working version of the new AKSequencer with AKCallbackInstrument

class SequencerWrapper {
var seq: AKSequencer!
var cbInst: AKCallbackInstrument!
var mixer: AKMixer!

init() {
mixer = AKMixer()
AudioKit.output = mixer
seq = AKSequencer()
cbInst = AKCallbackInstrument()

// set up a track
let track = seq.addTrack(for: cbInst)
for i in 0 ..< 4 {
track.add(noteNumber: 60, position: Double(i), duration: 0.5)
}
track.length = 4.0
track.loopEnabled = true
track >>> mixer // must send track to mixer

// set up the callback instrument
cbInst.callback = { status, note, vel in
guard let status = AKMIDIStatus(byte: status),
let type = status.type,
type == .noteOn else { return }
print("note on: \(note)")
// trigger sampler etc from here
}
cbInst >>> mixer // must send callbackInst to mixer
}

func play() {
seq.playFromStart()
}
}

How to visualize current AKSequencer position with Audiokit?

I do this with the AKCallbackInstrument. For each sequence track that I write MIDI events to, I have a sister sequencer track sending to AKCallbackInstrument. When I write a MIDI event for the audio track, I also write a GUI event to the sister callback track.

Because you can only send MIDIStatus, MIDINote, and MIDIVelocity data to the callback instrument, you have to arbitrarily encode information into these formats. For example, a MIDINote of 0 might signify one type of GUI event, MIDINote 1 something else. Creating some enums makes this easy.

Of course the callback functions are called on a background thread, so don't forget to specify that your GUI updates should happen on the main thread.

This approach has worked quite well for me.

edit: I suspect you've already seen this sample code that illustrates something very similar, but this link might be useful for anyone else coming across this question.

How to connect AKSequencer to a AKCallbackInstrument?

True, the class AKCallbackInstrument does not have a property midiIn, although the documentation does show it being used that way. Instead of using AKCallbackInstrument, use AKMIDICallbackInstrument. That class has midiIn, and seems to work fine.

AudioKit AKPlayer stop with at AVAudioTime method

The most accurate way to schedule events in AudioKit is by using AKSequencer. The sequencer can be connected to a callback instrument, which is a node that passes the events to an user-defined function.

In your case, you would add an event at the time where you want the player to stop. In your callback function, you would stop the player as a response to that event.

This is an outline of what should be done:

  1. Create a track to contain the stop event, 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 stop event to the track, at the time position where you want the music to stop. As you will be interpreting the event yourself with a callback function, it doesn't really matter what type of event you use. You could simply use a Note On.
  3. In the callback function, stop the player when that event is received.

This is what your callback function would look like:

func stopCallback(status:UInt8, note:MIDINoteNumber, vel:MIDIVelocity) -> () {
guard let status = AKMIDIStatus(byte: status),
let type = status.type,
type == .noteOn else { return }
drums.stop()
}

AudioKit ios AKSequencer Not Restarting Playback Accurately

There are a few confusing things in your code, so I'm not sure if this is your only issue, but minimally, every time you change the length of your sequence, you will need to call setLength() followed by enableLooping. Basically, by default (i.e., unless you explicitly set the length) the length of the sequence will be the length of the longest track in the sequence. In your 'playback' method you are adding track on top of track without removing the old ones so it has no way of knowing how long you intend the sequence to be.

Your 'playback' method is doing two distinct things (neither of which involves playback). You might want to break it up. You could have a setup() to do the things that only ever need to be done once (create the tracks, set their outputs, set up the callback) and a rewriteSequence() methods that gets called when you want to re-write the tracks. This way you can reuse your existing tracks rather than continuously creating new ones.

var metronomeTrack: AKMusicTrack!
var callbackTrack: AKMusicTrack!

// call this just once at the start
func setup() {
metronomeTrack = sequencer.newTrack()
metronomeTrack?.setMIDIOutput(click.midiIn)
callbackTrack = sequencer.newTrack()
callbackTrack?.setMIDIOutput(callbackInst.midiIn)

callbackInst.callback = {status, noteNumber, velocity in
guard status == .noteOn else { return }
print("beat number: \(noteNumber + 1)")

}
}

// call this each time you want to change the sequence
func rewriteSequence() {
metronomeTrack?.clear()
callbackTrack?.clear()
for steps in 0 ... Int(measuresRowOneValue) {
metronomeTrack?.add(noteNumber: 60, velocity: 100, position: AKDuration(beats: Double(steps)), duration: AKDuration(beats: 0.5))
callbackTrack?.add(noteNumber: MIDINoteNumber(steps), velocity: 100, position: AKDuration(beats: Double(steps)), duration: AKDuration(beats: 0.5))
}

// This will make sure it loops correctly:
sequencer.setLength(AKDuration(beats: Double(measuresRowOneValue)))
sequencer.enableLooping()
}

I hope this helps.

AudioKit AKPlayer can not loopback to beginning with setPosition

If you intend to develop some kind of looper application, I think the approach is to use a sequencer to coordinate the playback of your audio files. At each position where a sound should start playing, you add an event to the sequence.

The sequencer can be connected to a callback instrument, which is a node that passes the events to an user-defined function. In your callback function, you would start playback of the audio file indicated by that event.

This is an outline of what should be done:

  1. Create a track to contain the playback 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. Set the sequencer's and the track's length to the total duration of the song (notice these properties are represented in beats instead of seconds). Set the sequencer's loopEnabled as desired.
  2. Add the playback events to the track, at the time positions where you want each sound to play. As you will be interpreting the events yourself with a callback function, it doesn't really matter what type of event you use. You could simply use a Note On.
  3. In the callback function, start playing the corresponding sound when that event is received.

You would need a couple of classes to represent some basic information about where each sample occurs in the song:

class Beat {
var sample: Sample!
var onsetTime: Double

var endTime {
get {
return onsetTime + sample.duration
}
}
}

class Sample {
var url: URL!
var duration: Double
}

class ViewController: UIViewController {
var samples: [Sample] = []
var beats: [Beat] = []

var player: AKPlayer!
var sequencer: AKSequencer!
}

On the note’s pitch you would store the index of the sample you want to play. This is what your callback function would look like:

func playCallback(status:UInt8, note:MIDINoteNumber, vel:MIDIVelocity) -> () {
guard let status = AKMIDIStatus(byte: status),
let type = status.type,
type == .noteOn else { return }
DispatchQueue.main.async {
player.load(samples[note].url)
player.play()
}
}

You can use AKSequencer’s seek method to start playing your song at some arbitrary position.

There is a special case where the sequencer starts to play in the middle of a beat. In that case, you must manually start the playback of that sample.

func play(at time: Double = 0) {
sequencer.seek(time)
sequencer.play()
for beat in beats {
if beat.onsetTime < time && time < beat.endTime {
player.load(beat.sample.url)
player.play(from: time - beat.onsetTime)
}
}
}


Related Topics



Leave a reply



Submit