Я пытаюсь написать класс MIDIPlayer
, который является оберткой для AVAudioEngine
и AVAudioUnitMIDIInstrument
. Я написал al oop, который получает имена и ASBD для всех AudioComponents
типа MusicDevice
, а затем выбирает наиболее желаемую единицу в соответствии со списком, который работает очень похоже на замену шрифта, с Apples DLS MusicDevice в качестве окончательного запасного варианта. , Вот мой пример кода:
import AVKit
fileprivate func setupAVAudioEngine(engine: inout AVAudioEngine, instrumentAU: inout AVAudioUnitMIDIInstrument?) {
var instrumentACD = AudioComponentDescription(componentType: kAudioUnitType_MusicDevice, componentSubType: 0, componentManufacturer: 0, componentFlags: 0, componentFlagsMask: 0)
var instrumentComponents: [(AudioComponentDescription, String)] = []
var instrumentComponent: AudioComponent? = nil
repeat {
instrumentComponent = AudioComponentFindNext(instrumentComponent, &instrumentACD)
if instrumentComponent == nil {
break
}
var compDescr = AudioComponentDescription()
var name: Unmanaged<CFString>?
AudioComponentCopyName(instrumentComponent!, &name)
AudioComponentGetDescription(instrumentComponent!, &compDescr)
let nameString = name!.takeRetainedValue() as String
instrumentComponents.append((compDescr, nameString))
name?.release()
} while true
let instrumentComponentSubstitutionList = ["MakeMusic: SmartMusicSoftSynth","Apple: AUMIDISynth","Apple: DLSMusicDevice"]
var found = false
for instrument in instrumentComponentSubstitutionList {
for member in instrumentComponents {
if member.1 == instrument {
instrumentACD = member.0
print("\(member.1) found")
found = true
break
}
if found {break}
}
}
print("Try to create InstrumentNode with ACD: \(instrumentACD)")
instrumentAU = AVAudioUnitMIDIInstrument(audioComponentDescription: instrumentACD)
print("InstrumentNode created: \(instrumentAU!.name)")
print()
engine.attach(instrumentAU!)
engine.connect(instrumentAU!, to: engine.mainMixerNode, format: nil)
}
open class MIDIPlayer {
private var audioEngine: AVAudioEngine
private var instrumentUnit: AVAudioUnitMIDIInstrument
private var mainMixer: AVAudioMixerNode
public init() {
self.audioEngine = AVAudioEngine()
self.mainMixer = audioEngine.mainMixerNode
var instrumentAU: AVAudioUnitMIDIInstrument?
setupAVAudioEngine(engine: &audioEngine, instrumentAU: &instrumentAU)
self.instrumentUnit = instrumentAU!
try! audioEngine.start()
}
public func playMIDINote(_ note: UInt8) {
print("Playing MIDI Note \(note)")
instrumentUnit.startNote(note, withVelocity: 70, onChannel: 0)
sleep(1)
instrumentUnit.stopNote(note, onChannel: 0)
}
}
let midiPlayer = MIDIPlayer()
midiPlayer.playMIDINote(60)
midiPlayer.playMIDINote(62)
midiPlayer.playMIDINote(64)
midiPlayer.playMIDINote(65)
midiPlayer.playMIDINote(67)
Код прекрасно работает на игровой площадке Xcode 11.3.1, однако, когда я использую тот же самый код за пределами игровой площадки, я получаю различные виды ошибок: Когда я копирую тот же код в проект командной строки и запустить его из XCode, он все еще работает, но консоль выдает мне следующую ошибку:
[AudioHAL_Client] AudioHardware.cpp:666:AudioObjectGetPropertyData: AudioObjectGetPropertyData: no object with given ID 0
Когда я запускаю исполняемый файл без Xcode, об ошибке не сообщается .
Когда я создаю одно приложение View, помещаю класс и его настройки c в его собственный исходный файл и создаю экземпляр в методе applicationDidFinishLaunching
AppDelegate, я получаю следующие ошибки:
[AudioHAL_Client] HALC_ShellDriverPlugIn.cpp:104:Open: HALC_ShellDriverPlugIn::Open: opening the plug-in failed, Error: 2003329396 (what)
[AudioHAL_Client] AudioHardware.cpp:666:AudioObjectGetPropertyData: AudioObjectGetPropertyData: no object with given ID 0
Эти ошибки записываются в консоль даже до вызова моей функции настройки. Тем не менее, код все еще работает (я слышу ноты), но только если я изменю instrumentComponentSubstitutionList
, чтобы был найден один из Apple AU (другими словами: не SmartMusicSoftSynth). Когда я сохраняю SoftSynth на предпочитаемом устройстве, код вылетает, и я получаю дополнительную ошибку:
[avae] AVAEInternal.h:103:_AVAE_CheckNoErr: [AUInterface.mm:461:AUInterfaceBaseV3: (AudioComponentInstanceNew(comp, &_auv2)): error -3000
Примечание. На игровой площадке и в приложении командной строки работает SoftSynth.
Некоторые наблюдения, которые могут относиться или не относиться к проблеме:
В этом посте http://www.rockhoppertech.com/blog/multi-timbral-avaudiounitmidiinstrument/ Джин ДеЛиза упоминает, что AVAudioUnitSampler
является единственным подкласс абстрактного класса AVAudioUnitMIDIInstrument
. Это сообщение с 2016 года, но я не нахожу никакой дополнительной информации об этом. Но, очевидно, DLS MusicDevice, а также сторонний SoftSynth работают - по крайней мере, в некоторых средах.
ASBD, взятый из найденного AudioComponents
для обоих блоков Apple, имеет их свойство componentFlags
установлено на 2. В документации сказано: должно быть установлено на ноль, если не запрошено известное значение c. Свойство componentFlags
SoftSynth равно 0.
У меня все еще есть какая-то проблема, связанная с попыткой захвата звука с входа Как разрешить Xcode получить доступ к микрофону ? - связано с тем, что приложение командной строки показывает другое поведение при запуске из XCode или через терминал, и в обеих проблемах участвует CoreAudio.
Мой вопрос (ы):
Почему я получаю эти ошибки?
Почему сторонний плагин работает на игровой площадке и в приложении командной строки, но не в одном приложении просмотра?
Готов ли AVAudioEngine к размещать другие приборные модули, кроме монотибрального блока SamplerUnit?
Или мне нужно уйти в отставку и использовать приборы прибора напрямую, а не оболочку AVAudioUnitMIDIInstrument
?