问题
I'm trying to use the receivedMIDINoteOn function to flash a UILabel when the sequencer is playing a note. I have tried using the AKMIDIListener protocol with no success. Also I have made a sub class of AKMIDISampler and send midi to it from the sequencer. It plays the midi but the receivedMIDINoteOn is not called.
This is what I have in the init() of the conductor:
init() {
[ahSampler, beeSampler, gooSampler,flasher] >>> samplerMixer
AudioKit.output = samplerMixer
AudioKit.start()
let midi = AKMIDI()
midi.createVirtualPorts()
midi.openInput("Session 1")
midi.addListener(self)
}
The conductor follows AKMIDIListener protocol
this is the function: it is never called
func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel)
{
print("got it")
}
And this is the sub class of AKMIDISampler, it gets midi and plays the sine synth, but the receivedMIDINoteOn is never called.
class Flasher: AKMIDISampler
{
override func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel)
{
print("Flasher got it!")
}
}
edit: I should have been using the AKCallbackInstrument class instead, and overriding it's start() function.
回答1:
Ben,
Without seeing your entire project, I would guess that if your project is able to receive and trigger MIDI notes, then it's an issue with only sending its output to the UILabel. I recommend using NotificationCenter to inform the ViewController when a MIDI event has been received in the Conductor
class. Be sure to add the DispatchQueue.main.async
code, or else the text won't update as expected. This was noted in the AudioKit Google Group here.
Example:
DispatchQueue.main.async(execute: {
nc.post(name: NSNotification.Name(rawValue: "outputMessage"),
object: nil,
userInfo: [
"message": self.outputMIDIMessage,
"midiSignalReceived": self.midiSignalReceived,
"midiTypeReceived": self.midiTypeReceived
])
})
I would also recommend the following:
Move let midi = AKMIDI()
into an instance variable outside of the init()
at the top of your Conductor class, rather than inside of it. It looks like you're attempting to create it after AudioKit.start()
.
I posted a sample project that demonstrates how you can change a UILabel's color whenever a MIDI note number has been received via AudioKit:
https://github.com/markjeschke/AKMidiReceiver
Conductor class:
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
}
}
}
ViewController with the UILabel:
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.")
}
}
}
I hope this helps. Please let me know if you have any questions about this.
Take care,
Mark
P.S. If you clone this AKMidiReceiver example, open the Workspace, and no scheme appears in the Xcode project, please follow these steps that were found here:
- Click on No Scheme
- Click on Manage Scheme
- Click on Autocreate Schemes Now
回答2:
Depending on how you initialize flasher, you may have to run flasher.enableMIDI() optionally with names.
来源:https://stackoverflow.com/questions/48302292/audiokit-ios-receivedmidinoteon-function