AudioKit iOS - 收到MIDINoteOn 功能

Posted

技术标签:

【中文标题】AudioKit iOS - 收到MIDINoteOn 功能【英文标题】:AudioKit iOS - receivedMIDINoteOn function 【发布时间】:2018-01-17 13:26:49 【问题描述】:

我正在尝试使用 receivedMIDINoteOn 函数在音序器播放音符时闪烁 UILabel。我尝试使用 AKMIDIListener 协议没有成功。我还制作了一个 AKMIDISampler 的子类,并从音序器向它发送 midi。它播放 MIDI,但没有调用收到的 MIDINoteOn。

这就是我在导体的 init() 中所拥有的:

init() 

    [ahSampler, beeSampler, gooSampler,flasher] >>> samplerMixer
    AudioKit.output = samplerMixer
    AudioKit.start()



    let midi = AKMIDI()
    midi.createVirtualPorts()
    midi.openInput("Session 1")
    midi.addListener(self)

指挥遵循AKMIDIListener协议

这是函数:它永远不会被调用

func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel)

    print("got it")

这是 AKMIDISampler 的子类,它获取 midi 并播放正弦合成器,但从未调用收到的 MIDINoteOn。

class Flasher: AKMIDISampler

    override func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity:    MIDIVelocity, channel: MIDIChannel)
    
        print("Flasher got it!")
    

编辑:我应该改用 AKCallbackInstrument 类,并覆盖它的 start() 函数。

【问题讨论】:

【参考方案1】:

本,

没有看到您的整个项目,我猜如果您的项目能够接收和触发 MIDI 音符,那么仅将其输出发送到 UILabel 是一个问题。我建议在Conductor 类中收到 MIDI 事件时使用 NotificationCenter 通知 ViewController。请务必添加DispatchQueue.main.async 代码,否则文本不会按预期更新。 AudioKit Google Group here 已注意到这一点。

示例:

DispatchQueue.main.async(execute: 
        nc.post(name: NSNotification.Name(rawValue: "outputMessage"),
                object: nil,
                userInfo: [
                    "message": self.outputMIDIMessage,
                    "midiSignalReceived": self.midiSignalReceived,
                    "midiTypeReceived": self.midiTypeReceived
        ])
)

我还推荐以下内容:

let midi = AKMIDI() 移动到您的 Conductor 类顶部的 init() 之外的实例变量中,而不是在其内部。您似乎正试图在 AudioKit.start() 之后创建它。

我发布了一个示例项目,演示如何在通过 AudioKit 接收到 MIDI 音符编号时更改 UILabel 的颜色:

https://github.com/markjeschke/AKMidiReceiver

指挥类:

import AudioKit

enum MidiEventType: String 
    case
        noteNumber          = "Note Number",
        continuousControl   = "Continuous Control",
        programChange       = "Program Change"


class Conductor: AKMIDIListener 

    // Globally accessible
    static let sharedInstance = Conductor()

    // Set the instance variables outside of the init()
    let midi = AKMIDI()

    var demoSampler = SamplerAudioFileLoader()
    var samplerMixer = AKMixer()
    var outputMIDIMessage = ""
    var midiSignalReceived = false
    var midiTypeReceived: MidiEventType = .noteNumber

    init() 

        // Session settings
        AKSettings.bufferLength = .medium
        AKSettings.defaultToSpeaker = true

        // Allow audio to play while the ios device is muted.
        AKSettings.playbackWhileMuted = true

        do 
            try AKSettings.setSession(category: .playAndRecord, with: [.defaultToSpeaker, .allowBluetooth, .mixWithOthers])
         catch 
            AKLog("Could not set session category.")
        

        // File path options are:
        // "TX Brass"
        // "TX LoTine81z"
        // "TX Metalimba"
        // "TX Pluck Bass"
        demoSampler.loadEXS24Sample(filePath: "TX Brass")

        // If you wish to load a wav file, comment the `loadEXS24` method and uncomment this one:
//      demoSampler.loadWavSample(filePath: "Kick") // Load Kick wav file

        [demoSampler] >>> samplerMixer
        AudioKit.output = samplerMixer
        AudioKit.start()

        // MIDI Configure
        midi.createVirtualInputPort(98909, name: "AKMidiReceiver")
        midi.createVirtualOutputPort(97789, name: "AKMidiReceiver")
        midi.openInput()
        midi.openOutput()
        midi.addListener(self)

    

    // Capture the MIDI Text within a DispatchQueue, so that it's on the main thread.
    // Otherwise, it won't display.
    func captureMIDIText() 
        let nc = NotificationCenter.default
        DispatchQueue.main.async(execute: 
            nc.post(name: NSNotification.Name(rawValue: "outputMessage"),
                    object: nil,
                    userInfo: [
                        "message": self.outputMIDIMessage,
                        "midiSignalReceived": self.midiSignalReceived,
                        "midiTypeReceived": self.midiTypeReceived
                ])
        )
    

    // MARK: MIDI received

    // Note On Number + Velocity + MIDI Channel
    func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel) 
        midiTypeReceived = .noteNumber
        outputMIDIMessage = "\(midiTypeReceived.rawValue)\nChannel: \(channel+1)  noteOn: \(noteNumber)  velocity: \(velocity)"
        print(outputMIDIMessage)
        midiSignalReceived = true
        captureMIDIText()
        playNote(note: noteNumber, velocity: velocity, channel: channel)
    

    // Note Off Number + Velocity + MIDI Channel
    func receivedMIDINoteOff(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel) 
        midiTypeReceived = .noteNumber
        outputMIDIMessage = "\(midiTypeReceived.rawValue)\nChannel: \(channel+1)  noteOff: \(noteNumber)  velocity: \(velocity)"
        print(outputMIDIMessage)
        midiSignalReceived = false
        captureMIDIText()
        stopNote(note: noteNumber, channel: channel)
    

    // Controller Number + Value + MIDI Channel
    func receivedMIDIController(_ controller: MIDIByte, value: MIDIByte, channel: MIDIChannel) 
        // If the controller value reaches 127 or above, then trigger the `demoSampler` note.
        // If the controller value is less, then stop the note.
        // This creates an on/off type of "momentary" MIDI messaging.
        if value >= 127 
            playNote(note: 30 + controller, velocity: 80, channel: channel)
         else 
            stopNote(note: 30 + controller, channel: channel)
        
        midiTypeReceived = .continuousControl
        outputMIDIMessage = "\(midiTypeReceived.rawValue)\nChannel: \(channel+1)  controller: \(controller)  value: \(value)"
        midiSignalReceived = true
        captureMIDIText()
    

    // Program Change Number + MIDI Channel
    func receivedMIDIProgramChange(_ program: MIDIByte, channel: MIDIChannel) 
        // Trigger the `demoSampler` note and release it after half a second (0.5), since program changes don't have a note off release.
        triggerSamplerNote(program, channel: channel)
        midiTypeReceived = .programChange
        outputMIDIMessage = "\(midiTypeReceived.rawValue)\nChannel: \(channel+1)  programChange: \(program)"
        midiSignalReceived = true
        captureMIDIText()
    

    func receivedMIDISetupChange() 
        print("midi setup change")
        print("midi.inputNames: \(midi.inputNames)")

        let listInputNames = midi.inputNames

        for inputNames in listInputNames 
            print("inputNames: \(inputNames)")
            midi.openInput(inputNames)
        
    

    func playNote(note: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel) 
        demoSampler.play(noteNumber: note, velocity: velocity, channel: channel)
    

    func stopNote(note: MIDINoteNumber, channel: MIDIChannel) 
        demoSampler.stop(noteNumber: note, channel: channel)
    

    func triggerSamplerNote(_ program: MIDIByte, channel: MIDIChannel) 
        playNote(note: 60 + program, velocity: 80, channel: channel)
        let releaseNoteDelay = DispatchTime.now() + 0.5 // Change 0.5 to desired number of seconds
        DispatchQueue.main.asyncAfter(deadline: releaseNoteDelay) 
            self.stopNote(note: 60 + program, channel: channel)
            self.midiSignalReceived = false
        
    


带有 UILabel 的 ViewController:

import UIKit
import AudioKit

class ViewController: UIViewController 

    @IBOutlet weak var outputTextLabel: UILabel!

    var conductor = Conductor.sharedInstance
    var midiSignalReceived = false
    var midiTypeReceived: MidiEventType = .noteNumber

    override func viewDidLoad() 
        super.viewDidLoad()

        let nc = NotificationCenter.default
        nc.addObserver(forName:NSNotification.Name(rawValue: "outputMessage"), object:nil, queue:nil, using:catchNotification)
    

    override func viewDidAppear(_ animated: Bool) 
        super.viewDidAppear(animated)
        flashBackgroundColor()
        midiSignalReceived = false
        self.outputTextLabel.text = "Listening for MIDI events..."
    

    @objc func catchNotification(notification:Notification) -> Void 
        guard
            let userInfo = notification.userInfo,
            let message  = userInfo["message"] as? String,
            let midiSignalReceived = userInfo["midiSignalReceived"] as? Bool,
            let midiTypeReceived = userInfo["midiTypeReceived"] as? MidiEventType else 
                print("No userInfo found in notification")
                return
        
        DispatchQueue.main.async(execute: 
            self.outputTextLabel.text = message
            self.midiSignalReceived = midiSignalReceived
            self.midiTypeReceived = midiTypeReceived
            self.flashBackgroundColor()
        )
    

    @objc func flashBackgroundColor() 
        if midiSignalReceived 
            self.outputTextLabel.backgroundColor = UIColor.green
            self.view.backgroundColor = UIColor.lightGray
            if midiTypeReceived != .noteNumber 
                self.perform(#selector(dismissFlashBackgroundColor), with: nil, afterDelay: 0.5)
            
         else 
            dismissFlashBackgroundColor()
        
    

    @objc func dismissFlashBackgroundColor() 
        UIView.animate(withDuration: 0.5) 
            self.outputTextLabel.backgroundColor = UIColor.clear
            self.view.backgroundColor = UIColor.white
            self.midiSignalReceived = false
            self.conductor.midiSignalReceived = false
        
    

    deinit 
        NotificationCenter.default.removeObserver(self,
                                                  name: NSNotification.Name(rawValue: "outputMessage"),
                                                  object: nil)
    


SamplerAudioFileLoader.swift:

import AudioKit

class SamplerAudioFileLoader: AKMIDISampler 

    internal func loadWavSample(filePath: String) 
        do 
            try self.loadWav("Sounds/\(filePath)")
         catch 
            print("Could not locate the Wav file.")
        
    

    internal func loadEXS24Sample(filePath: String) 
        do 
            try self.loadEXS24("Sounds/Sampler Instruments/\(filePath)")
         catch 
            print("Could not locate the EXS24 file.")
        
    


我希望这会有所帮助。如果您对此有任何疑问,请告诉我。

保重, 标记

附:如果你克隆了这个AKMidiReceiver example,打开Workspace,Xcode项目中没有出现scheme,请按照here找到的步骤操作:

    点击无方案 点击管理方案 立即点击自动创建方案

【讨论】:

您的项目在很多方面都有帮助!所以首先感谢分享。我实际上正在尝试做一些不同的事情:我想使用 AKSequencer 的 midi 输出来刷新 UILabel,因此不涉及来自外部设备的 midi 输入。我已经在您的项目中实现了一个音序器,并注意到即使采样器正在播放 receivedMIDINoteOn 函数也没有被调用,所以我想我可能是在错误的方向。你知道当 MIDI 来自音序器时是否可以调用 receivedMIDINoteOn 函数?如果没有,我还能做什么? 显然我走错了方向,应该使用 AKCallbackInstrument 代替。感谢 DispatchQueue.main.async 提示!最后它确实派上用场了,现在我闪烁了 :) 很高兴我能提供帮助,并且您能够通过 AKCallbackInstrument 解决闪烁的指示器。如果您的音序器公开,我很乐意看到它在运行。 当然。我很乐意。 @BenSpector,我用 Xcode 11.2.1 和 AudioKit 4.9.2 更新了这个 GitHub 示例:github.com/markjeschke/AKMidiReceiver【参考方案2】:

根据您初始化 flasher 的方式,您可能需要使用名称运行 flasher.enableMIDI()。

【讨论】:

感谢您的回答,我在 AudioKit.start() 之后添加了 flasher.enableMIDI() 行,但没有任何变化。不知道如何使用带有名称的那个,真的可以在那里使用一些帮助。还有如何让 AKMIDIListener 调用 receivedMIDINoteOn 函数?非常感谢。 顺便说一句,闪光灯正在播放我发送的midi,它只是没有调用received函数receivedMIDINoteOn。

以上是关于AudioKit iOS - 收到MIDINoteOn 功能的主要内容,如果未能解决你的问题,请参考以下文章

AudioKit (iOS) - 为频率/幅度变化添加观察者

AudioKit V4.1 和版本 9.3 beta (9Q98q)

如何修复 iOS 13.1.2 AudioKit.renderToFile 异常:AUGraphParser::InitializeActiveNodesInInputChain(ThisGraph,

在 IOS 上使用 AudioKit 将声音文件作为 MIDI 音符发送

AudioKit iOS 设置输入输出设备

AudioKit 教程:入门