TL; DR: я могу менять MIDI-инструменты в своем приложении iOS при работе в симуляторе Xcode, но не при работе на устройстве.
Я следил за несколькими из Джина Де Лизы посты, чтобы узнать, как использовать MIDI в приложении iOS. Я продвинулся дальше, следуя мультитембральному AVAudioUnitMIDIInstrument для подкласса AVAudioUnitMIDIInstrument
и используя свойство kMusicDeviceProperty_SoundBankURL
, чтобы указать на файл звукового шрифта sf2.
В симуляторе Xcode , это прекрасно работает. Я могу sendProgramChange
переключиться на любой стандартный MIDI-инструмент. Но когда я загружаю свое приложение на iPhone, только пианино, MIDI-инструмент 1, воспроизводит любой звук. Кто-нибудь еще видел такое поведение?
Код, который я использую, основан на шаблоне "iOS Game" в Xcode 11.4 с четырьмя изменениями.
Первое изменение - настройка аудио сессия для приложения правильно. Мой новый AppDelegate.application
:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.playback, options: .duckOthers)
} catch let error as NSError {
print("Unable to set audio category: \(error.localizedDescription)")
}
do {
try audioSession.setActive(true)
} catch let error as NSError {
print("Unable to activate audio session: \(error.localizedDescription)")
}
return true
}
Второй - мой подкласс AVAudioUnitMIDIInstrument
:
class MyMIDIInstrument: AVAudioUnitMIDIInstrument {
init(soundBankURL: URL) throws {
let description = AudioComponentDescription(
componentType: kAudioUnitType_MusicDevice, componentSubType: kAudioUnitSubType_MIDISynth, componentManufacturer: kAudioUnitManufacturer_Apple, componentFlags: 0, componentFlagsMask: 0
)
super.init(audioComponentDescription: description)
var bankURL = soundBankURL
let status = AudioUnitSetProperty(
self.audioUnit,
AudioUnitPropertyID(kMusicDeviceProperty_SoundBankURL),
AudioUnitScope(kAudioUnitScope_Global),
0,
&bankURL,
UInt32(MemoryLayout<URL>.size))
if (status != OSStatus(noErr)) {
throw NSError(domain: "MyMIDIInstrument", code: Int(status), userInfo: [NSLocalizedDescriptionKey: "Could not set soundbank property"])
}
}
}
Последнее изменение кода - добавление инициализации и воспроизведение в GameScene
:
class GameScene: SKScene {
var avEngine = AVAudioEngine()
var midi: MyMIDIInstrument?
override func didMove(to view: SKView) {
guard let soundBankURL = Bundle.main.url(forResource: "FluidR3_GM", withExtension: "sf2")
else {
print("Failed to get url for sound bank")
exit(-1)
}
do {
try midi = MyMIDIInstrument(soundBankURL: soundBankURL)
} catch let error as NSError {
print("Failed to init MIDI: \(error.code) - \(error.localizedDescription)")
exit(-1)
}
avEngine.attach(midi!)
avEngine.connect(midi!, to: avEngine.mainMixerNode, format: nil)
do {
try avEngine.start()
} catch let error as NSError {
print("Failed to start AVEngine: \(error.localizedDescription)")
}
// Case 1: do not send program change
//
// Results:
// Simulator: piano sounds
// iPhone: piano sounds
// Case 2: send program change to 0=piano, without bank
// midi!.sendProgramChange(UInt8(0), onChannel: UInt8(0))
// Results:
// Simulator: piano sounds
// iPhone: piano sounds
// Case 3: send program change to 0=piano, with bank=melodic
// midi!.sendProgramChange(UInt8(0), bankMSB: UInt8(kAUSampler_DefaultMelodicBankMSB), bankLSB: UInt8(kAUSampler_DefaultBankLSB), onChannel: UInt8(0))
// Results:
// Simulator: no sound
// iPhone: no sound
// Case 4: send program change to 0=piano, with bank=0
// midi!.sendProgramChange(UInt8(0), bankMSB: UInt8(0), bankLSB: UInt8(kAUSampler_DefaultBankLSB), onChannel: UInt8(0))
// Results:
// Simulator: piano sounds
// iPhone: piano sounds
// Case 5: send program change to 12=marimba, without bank
// midi!.sendProgramChange(UInt8(12), onChannel: UInt8(0))
// Results:
// Simulator: marimba sounds
// iPhone: no sound
// Case 6: send program change to 12=marimba, with bank=melodic
// midi!.sendProgramChange(UInt8(12), bankMSB: UInt8(kAUSampler_DefaultMelodicBankMSB), bankLSB: UInt8(kAUSampler_DefaultBankLSB), onChannel: UInt8(0))
// Results:
// Simulator: no sound
// iPhone: no sound
// Case 7: send program change to 12=marimba, with bank=0
// midi!.sendProgramChange(UInt8(12), bankMSB: UInt8(0), bankLSB: UInt8(kAUSampler_DefaultBankLSB), onChannel: UInt8(0))
// Results:
// Simulator: marimba sounds
// iPhone: no sound
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("playing note")
midi!.startNote(64, withVelocity: 64, onChannel: 0)
}
}
И последнее изменение - добавить в проект звуковой шрифт "FluidR3_GM.sf2" .
В GameScene.didMove(toView:)
вы можете увидеть различные варианты поведения, которые я ' видел. Я нахожу несколько интересных вещей:
Я могу получить звуки фортепиано как в симуляторе, так и на устройстве, любым из трех способов: никогда не вызывая sendProgramChange
, звоня sendProgramChange
без указания банка MSB / LSB или путем вызова sendProgramChange
с MSB, установленным в ноль. Однако, если я позвоню sendProgramChange
, указав MSB как kAUSampler_DefaultMelodicBankMSB
, как и все инструкции, которые я видел, говорят, что вы должны, я не получу звука.
Я могу получить звуки маримбы из симулятор, позвонив по номеру sendProgramChange
любым способом, который помог получить звуки фортепиано. Но ни один из этих методов не работает на устройстве.
Я пытался провести тот же эксперимент со звуковым шрифтом GeneralUser GS , с теми же результатами.
Итак, кто-нибудь имеет представление о том, чего мне не хватает?