Reputation: 383
I'm trying to control an oscillator with a sequencer with AudioKit v5, and I've hit a snag. I'm subclassing MIDIInstrument
, but I'm not sure if this is right. Please see code below.
I'm getting this error on startup:
AVAEInternal.h:76 required condition is false: [AVAudioEngine.mm:413:AttachNode: (node != nil)]
There's a previous post about this with an older AK version, which was somewhat helpful, but none of the links to examples in it work: How do I control an oscillator's frequency with a sequencer
Can you point me in the right direction? Many thanks!
EDIT: Slight progress? I misunderstood the AudioEngine
function and had 2 instances, so I removed one, which cleared the error. And adding track!.setMIDIOutput(instrument.midiIn)
has it logging the 4 notes now, but still no sound. MIDIInstrument
seems to accept a MIDIClientRef
, but I see no reference to that in the sequencer class...
import AudioKit
import CAudioKit
class Test2 {
var instrument: OscMIDIInstrument
var sequencer: AppleSequencer
init() {
instrument = OscMIDIInstrument()
sequencer = AppleSequencer()
sequencer.setGlobalMIDIOutput(instrument.midiIn)
instrument.enableMIDI()
let track = sequencer.newTrack()
track!.setMIDIOutput(instrument.midiIn)
for i in 0 ..< 4 {
track!.add(noteNumber: 60, velocity: 64, position: Duration(seconds: Double(i)), duration: Duration(seconds: 0.5))
}
}
func testButton() {
if sequencer.isPlaying {
sequencer.stop()
} else {
sequencer.rewind()
sequencer.play()
}
}
}
class OscMIDIInstrument: MIDIInstrument {
var akEngine: AudioEngine
var osc: Oscillator
init() {
akEngine = AudioEngine()
osc = Oscillator()
super.init()
akEngine.output = osc
osc.amplitude = 0.1
osc.frequency = 440.0
do {
try akEngine.start()
} catch {
print("Couldn't start AudioEngine.")
}
}
override func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel, portID: MIDIUniqueID? = nil, offset: MIDITimeStamp = 0) {
osc.play()
}
override func receivedMIDINoteOff(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel, portID: MIDIUniqueID? = nil, offset: MIDITimeStamp = 0) {
osc.stop()
}
}
Upvotes: 1
Views: 211
Reputation: 383
Got it working, somewhat. I found this example which pointed me to MIDICallbackInstrument
:
https://github.com/AudioKit/Cookbook/blob/main/Cookbook/Cookbook/Recipes/CallbackInstrument.swift
The remaining issue is you apparently can't implement sysex messages this way, I guess because the callback messages are limited to 3 bytes.
So I'm still looking for a better solution if anyone can help.
Thanks a lot!
class Test {
let akEngine = AudioEngine()
let sequencer = AppleSequencer()
let osc = Oscillator()
init() {
let callbackInstrument = MIDICallbackInstrument { [self] status, note, _ in
guard let midiStatus = MIDIStatusType.from(byte: status) else {
return
}
if midiStatus == .noteOn {
print("NoteOn \(note) at \(sequencer.currentPosition.seconds)")
osc.play()
} else if midiStatus == .noteOff {
print("NoteOff \(note) at \(sequencer.currentPosition.seconds)")
osc.stop()
}
}
let track = sequencer.newTrack()
for i in 0..< 4 {
track!.add(noteNumber: 60, velocity: 64, position: Duration(seconds: Double(i)), duration: Duration(seconds: 0.25))
}
track?.setMIDIOutput(callbackInstrument.midiIn)
akEngine.output = osc
do {
try akEngine.start()
} catch {
print("Couldn't start AudioEngine.")
}
}
func play() {
if sequencer.isPlaying {
sequencer.stop()
} else {
sequencer.rewind()
sequencer.play()
}
}
}
Upvotes: 1