ios - AudioKit iOS - 接收 MIDINoteOn 函数

标签 ios swift midi audiokit

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

这是我在 conductor 的 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 协议(protocol)

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

func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel)
{
    print("got it")
}

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

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

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

最佳答案

本,

在没有看到您的整个项目的情况下,我猜想如果您的项目能够接收和触发 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 :

  1. 点击无方案
  2. 点击管理方案
  3. 立即点击自动创建方案

关于ios - AudioKit iOS - 接收 MIDINoteOn 函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48302292/

相关文章:

go - MIDI 消息的 OSC 消息格式

ios - AsyncDisplayKit 有 viewForHeaderInSection 的替代方案吗?

ios - 在 iOS 中指定位置时使用部分点(使用 CGPoint)

ios - 如何在选项卡栏 Controller 中单独启动 Viewcontroller

ios - 为什么我在尝试将 key 保存到钥匙串(keychain)时会返回 errSecParam (-50)?

c++ - 读取 midi 文件

python - 是否可以在 portmidi/pyportmidi 中找出 MIDI 设备连接到哪个 USB 端口

ios - perform segue with identifier 和 prepare for segue 之间的区别

ios - 将 Unity 与 native IOS 代码集成失败

ios - Swift UIDevice.currentDevice() 没有编译