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,