首页 > 解决方案 > 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:)中,您可以看到我见过的各种行为。我发现一些有趣的事情:

  1. 我可以通过以下三种方式中的任何一种在模拟器和设备上获得钢琴音色:从不调用sendProgramChange,在sendProgramChange不指定库 MSB/LSB 的情况下调用,或者sendProgramChange在 MSB 设置为零的情况下调用。但是,如果我调用sendProgramChange将 MSB 指定为kAUSampler_DefaultMelodicBankMSB,正如我所看到的所有说明所说的那样,我听不到任何声音。

  2. sendProgramChange我可以通过调用任何用于获取钢琴声音的方法来从模拟器中获取马林巴琴的声音。但是,这些方法都不能在设备上运行。

我用GeneralUser GS sound font进行了相同的实验,结果相同。

那么,有没有人知道我可能会错过什么?

标签: iosavfoundationmidi

解决方案


推荐阅读