ios - AVAudioUnitMidiInstrument 可以在模拟器中更改程序,但不能在设备上更改程序?
问题描述
TL;DR:在 Xcode 模拟器中运行时,我可以在我的 iOS 应用程序中更改 MIDI 乐器,但在设备上运行时不能。
我一直在关注 Gene De Lisa 的一些帖子,以了解如何在 iOS 应用程序中使用 MIDI。通过遵循Multi-timbral AVAudioUnitMIDIInstrument到 subclass ,我得到了最远的结果AVAudioUnitMIDIInstrument
,并使用该kMusicDeviceProperty_SoundBankURL
属性指向一个 sf2 声音字体文件。
在 Xcode 模拟器中,这很好用。我可以sendProgramChange
切换到任何标准的 MIDI 乐器。但是,当我将我的应用程序加载到 iPhone 上时,只有钢琴 MIDI 乐器 1 可以播放任何声音。有没有其他人看到过这种行为?
我使用的代码基于 Xcode 11.4 中的“iOS 游戏”模板,有四处更改。
第一个更改是正确设置应用程序的音频会话。我的新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 sound font进行了相同的实验,结果相同。
那么,有没有人知道我可能会错过什么?
解决方案
推荐阅读
- scala - 创建隐式类后如何测试我的scala json
- python - 在 Python 中检查两个列表之间的非常见字符串
- java - 在 Java 流编程中,为什么我无法将所有对象的某个字段设置为 null?
- java - 我们如何在 Spring Boot Filter 中获取和设置响应体
- javascript - 反应:数组包含元素,但 array.length 输出到控制台为零
- python-3.x - SqlAlchemy 中表内的多对一关系
- sql - 如何在聚合函数中找到前 5 个值
- c++ - 初始化 constexpr(字符串?)时使用 #include
- ios - 如何让托管的nodejs网站在cordova上运行?
- python - pyenv 在 MACOS 上安装 python 2.7.8 失败