Eggsalad
Eggsalad

Reputation: 383

Controlling oscillator with sequencer in AudioKit v5

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

Answers (1)

Eggsalad
Eggsalad

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

Related Questions