I'm using AudioKit 5.6.2 to create a simple Drum Sequencer. I'm using some Samples that already have reverb on them and therefore are quite long (e.g. 1.5-2.0 seconds). If I set the Duration to the length of the sample the following happens: Sample one plays, Sample two plays and will be cut off early by the note off of the first sample.
I got the following code for creating a simple Drum Sequencer, similar to the one in the AudioKit CookBook:
import SwiftUI
import AudioKit
import AVFoundation
class DrumsConductor: ObservableObject {
let engine = AudioEngine()
let sequencer = AppleSequencer()
private var drums = MIDISampler(name: "Drums")
var tempo: Double = 60.0 {
didSet {
sequencer.setTempo(tempo)
}
}
init() {
engine.output = drums
}
func destroy() {
drums.destroyEndpoint()
}
func playSequence() {
sequencer.stop()
sequencer.setTempo(tempo)
if sequencer.trackCount < 1 {
let _ = sequencer.newTrack()
}
sequencer.setGlobalMIDIOutput(drums.midiIn)
for index in 0..<8 {
sequencer.tracks[0].add(noteNumber: 48, velocity: 60, position: Duration(beats: Double(index)), duration: Duration(beats: 1.2))
}
sequencer.rewind()
sequencer.play()
}
func stopSequence() {
guard sequencer.isPlaying else { return }
sequencer.stop()
sequencer.preroll()
}
func start() {
do {
try engine.start()
} catch let err {
Log(err)
}
setupSound()
}
func stop() {
engine.stop()
}
func setupSound() {
let path = "Sounds/Sampler Instruments/Drums"
do {
try drums.loadSoundFont(path, preset: 0, bank: 0)
} catch {
fatalError(error.localizedDescription)
}
}
}
I tried to find a way to make the AppleSequencer or MIDISampler ignore noteOffs but couldn't find anything. I might be able to calculate the duration of each sample by measuring its distance to the next hit or I might use multiple tracks that are used alternating. But obviously I'd prefer a solution where the samples just are not cut off. Is there Anything I could do about it within existing AudioKit solutions?
Edit
I had this idea of copying MIDISampler to a new class DrumMIDISampler. I only changed the enableMIDI function to not send noteOffs.
public func enableMIDI(_ midiClient: MIDIClientRef = MIDI.sharedInstance.client,
name: String? = nil) {
let cfName = (name ?? self.name) as CFString
guard let midiBlock = avAudioNode.auAudioUnit.scheduleMIDIEventBlock else {
fatalError("Expected AU to respond to MIDI.")
}
CheckError(MIDIDestinationCreateWithBlock(midiClient, cfName, &midiIn) { packetList, _ in
for e in packetList.pointee {
print("Call: \(e)")
for event in e {
print("Event: \(event)")
event.data.withUnsafeBufferPointer { ptr in
guard let ptr = ptr.baseAddress else { return }
// Check if the event is a NoteOff event
let statusByte = ptr[0]
let isNoteOff = (statusByte >= 0x80 && statusByte <= 0x8F) || (ptr[0] == 0x90 && ptr[2] == 0) // NoteOff Event oder NoteOn Event mit einer Velocity von 0
// Only process the event if it's not a NoteOff event
if !isNoteOff {
midiBlock(AUEventSampleTimeImmediate, 0, event.data.count, ptr)
}
}
}
}
})
}
It works as expected in a debug configuration. But interestingly it doesn't work in a release environment. All MIDIPackets that I get are empty (see print("Call: \(e)")):
Debug:
Call: MIDIPacket(timeStamp: 3977539537470, length: 3, data: (144, 48, 60,...
Release:
MIDIPacket(timeStamp: 0, length: 0, data: (0, 0, 0,...
How is this possible and what can I do about it?