AudioKit: Problems connecting new input node to mixer

十年热恋 提交于 2021-02-11 14:53:25

问题


I have a function for changing the instruments in our app, at runtime. The intention is to allow for something like "presets" that can change the instrument setup — a little like what you'd see with a "song" file for a DAW. The problem I'm having is that new mixer connections don't seem to be made correctly, so that audio doesn't get from newly added instruments to the output.

My overall signal chain could be pictured like:

[(Instrument Node -> [Instrument Effects] -> AKBooster)] -> Instrument Mixer -> Master Effects -> Master Mixer -> Output

where the first chunk represents a set of instruments and their effects.

The function that manages switching the "song" is:

public func connect(trackMap: [Int : InstrumentDefinition], isReconnect: Bool) {
        
        if SequencerController.sharedInstance.synchronizeTracksToTrackMap(trackMap) {
            
            guard let sequencer = SequencerController.sharedInstance.sequencer else { return }
            guard let sequence = sequencer.sequence else { return }
            
            var midiInstruments = SequencerController.sharedInstance.midiInstruments
            // get track count AFTER synchronizing to trackMap
            var trackCount: UInt32 = 0
            var status = MusicSequenceGetTrackCount(sequence, &trackCount)
            if status != noErr {
                print("Conductor.connect(trackMap:) - Error getting track count: \(status)")
                return
            }
            
            for trackIndex in 3 ..< Int(trackCount) {
                let instrumentDef = trackMap[trackIndex]!
                var track: MusicTrack? = nil
                status = MusicSequenceGetIndTrack(sequence, UInt32(trackIndex), &track)
                if status != noErr {
                    print("Conductor.connect(trackMap:) - Error getting sequence track: \(status)")
                    // there's no track associated with this
                }
                if trackIndex == 3 {
                    if let soundID = instrumentDef.soundID {
                        InteractionController.sharedInstance.sequencerInterface.currentSound = soundID
                    }
                }
                if let track = track {
                    var midiInst: SQMIDIInstrument?
                    var didCreateInstrument = false
                    switch instrumentDef.instrumentType {
                    case .sampler:
                        if let inst = midiInstruments[trackIndex] {
                            inst.instrumentDefinition = instrumentDef
                            // While changing presets (or sources; synth/sampler) gate incoming events.
                            inst.gateEvents = true
                            // If this track contained a synth, change it to a sampler
                            if !(inst.instrumentBlock.type == .sampler) {
                                inst.instrumentBlock.type = .sampler
                            }
                            if let soundID = instrumentDef.soundID {
                                inst.instrumentBlock.changeSoundID(soundID)
                            }
                            self.instrumentBlocksByTrack[trackIndex] = inst.instrumentBlock
                            inst.gateEvents = false
                            midiInst = inst
                        } else {
                            // create a new SQMIDISampler and add
                            midiInst = SQMIDIInstrument(withInstrumentDefinition: instrumentDef)
                            // We're replacing this track, so nullify in current midiInstruments dictionary.
                            midiInstruments[trackIndex] = nil
                            self.instrumentBlocksByTrack[trackIndex] = midiInst?.instrumentBlock
                            didCreateInstrument = true
                        }
                    case .synth:
                        // create a new AKSynthOne-based instrument and set preset
                        if let inst = midiInstruments[trackIndex] {
                            inst.instrumentDefinition = instrumentDef
                            inst.gateEvents = true
                            // If this track contained a sampler, change it to a synth
                            if !(inst.instrumentBlock.type == .synth) {
                                inst.instrumentBlock.type = .synth
                            }
                            if let soundID = instrumentDef.soundID {
                                inst.instrumentBlock.changeSoundID(soundID)
                            }
                            self.instrumentBlocksByTrack[trackIndex] = inst.instrumentBlock
                            inst.gateEvents = false
                            midiInst = inst
                        } else {
                            // create a new SQMIDISampler and add
                            midiInst = SQMIDIInstrument(withInstrumentDefinition: instrumentDef)
                            // We're replacing this track, so nullify in current midiInstruments dictionary.
                            midiInstruments[trackIndex] = nil
                            self.instrumentBlocksByTrack[trackIndex] = midiInst?.instrumentBlock
                            didCreateInstrument = true
                        }
                    case .drumSampler:
                        if let inst = midiInstruments[trackIndex] {
                            inst.instrumentDefinition = instrumentDef
                            inst.gateEvents = true
                            if !(inst.instrumentBlock.type == .drumSampler) {
                                inst.instrumentBlock.type = .drumSampler
                            }
                            if let soundID = instrumentDef.soundID {
                                inst.instrumentBlock.changeSoundID(soundID)
                            }
                            self.instrumentBlocksByTrack[trackIndex] = inst.instrumentBlock
                            inst.gateEvents = false
                            midiInst = inst
                        } else {
                            midiInst = SQMIDIInstrument(withInstrumentDefinition: instrumentDef)
                            // We're replacing this track, so nullify in current midiInstruments dictionary.
                            midiInstruments[trackIndex] = nil
                            self.instrumentBlocksByTrack[trackIndex] = midiInst?.instrumentBlock
                            didCreateInstrument = true
                        }
                    default: ()
                    }
                    
                    if let inst = midiInst {
                        SequencerController.setMIDIOutput(inst.midiIn, forTrack: track)
                        if !isReconnect || didCreateInstrument {
                            self.instrumentMixer.connect(input: inst.instrumentBlock.booster)
                        }
                        midiInstruments[trackIndex] = midiInst
                    }
                }
            }
            // update the SequencerController's set of midiInstruments
            SequencerController.sharedInstance.midiInstruments = midiInstruments
        }
    }

The "midiInstruments" are AKMIDIInstrument subclasses that wrap a sampler and a synth with the instrument effects and booster (in an instrumentBlock), along with the instrument's parameters (in its instrumentDefinition). The synchronizeTracksToTrackMap() ensures that the sequence has the correct number of tracks. This function is owned by the Conductor, and the step that seems to fail is the self.instrumentMixer.connect(input: inst.instrumentBlock.booster) call. Specifically, I see the problem when this function adds a new track, requiring a new mixer connection (i.e., connect(input:)). I can see that inst is getting MIDI events, but there's no output. Strangely, once a new "song" has been created (which adds its track and instrument), we can switch to a different song, switch back, and the instruments are connected successfully.

Ultimately, we want to be able to swap out or reconfigure the first chunk of the signal chain outlined above; that is, the [(Instrument Node -> [Instrument Effects] -> AKBooster)] part, that represents an instrument (sampler/synth) and its effects. I can't find a reliable way of doing that and would greatly appreciate any advice anyone can give.

UPDATE: Following SamB's lead from here: How to reconnect AKPlayer and AKMixer after AudioKit.stop(), and using AudioKit.engine.connect(inst.instrumentBlock.booster.outputNode, to: self.instrumentMixer.avAudioUnitOrNode, format: nil) instead of just self.instrumentMixer.connect(input:), I was able to get closer. Still no sound, but the AKAmplitudeTap I put in my instrumentBlock (to peek in on what might be wrong) shows me there's at least signal there...


回答1:


Watching the WWDC15 video "What's new in Core Audio" it is made pretty clear that the process for building any AVAudioFoundation signal chain is to attach nodes first then connect them into graphs later. So I rewrote the logic of all my AudioKit code to jump down to Apple's methods and be explicit about attaching (or detaching) and connecting nodes—e.g.,

AudioKit.engine.attach(akNode.avAudioNode)
AudioKit.engine.connect(akNode.outputNode, to: mixer.avAudioUnitOrNode, format: nil)

Rethinking it in this way I was able to get things working as expected. From what I understand, the engine has to be off when attaching/detaching, so I surround any attach/detach operations with blocks to toggle the engine off/on. I would love to be able to avoid stopping audio, so if this isn't really necessary, any advice or clarification on that would be appreciated.

As convenient as AudioKit is, I think it's somewhat unfortunate that it hides the attach/connect distinction from the user by making everything look like a connection. If you're not already knowledgeable about Core Audio—e.g., coming to it primarily via AudioKit, as I did—I'd highly recommend thinking in terms of attach-first, connect-later semantics... Unless of course your use case will never require the total number of nodes to change. In our case, we needed to minimize attachments as much as possible.



来源:https://stackoverflow.com/questions/63865844/audiokit-problems-connecting-new-input-node-to-mixer

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!