How to Add .sf2 Instruments to Multiple Channels Using AVFAudio?

Hello everyone,

I'm relatively new to iOS development, and I'm currently working on a Flutter plugin package. I want to use the AVFAudio package to load instrument sounds from an SF2 file into different channels. Specifically, I'd like to load individual instruments from the SF2 file onto separate channels.

However, I've been struggling to find a way to achieve this. Could someone guide me on how to load SF2 instrument sounds into different channels using AVFAudio? I've tried various combinations of parameters (program number, soundbank MSB, and soundbank LSB), but none seem to work.

If anyone has experience with AVFAudio and SF2 files, I'd greatly appreciate your help. Perhaps there's a proven approach or a way to determine the correct values for these parameters? Should I use a soundfont editor to inspect specific values within the SF2 file?

Thank you in advance for any assistance!

Best regards, Melih

Replies

import Flutter
import CoreMIDI
import AVFAudio
import AVFoundation
import CoreAudio

public class FlutterMidiProPlugin: NSObject, FlutterPlugin {
  var _arguments = [String: Any]()
  var audioEngine = AVAudioEngine()
  var samplerNode = AVAudioUnitSampler()
    var tempFileURL = FileManager.default.temporaryDirectory.appendingPathComponent("temp.sf2")
    public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "flutter_midi_pro", binaryMessenger: registrar.messenger())
    let instance = FlutterMidiProPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
    case "loadSoundfont":
        guard let map = call.arguments as? Dictionary<String, Any>,
              let sf2Data = map["sf2Data"] as? FlutterStandardTypedData else {
            result(FlutterError(code: "INVALID_ARGUMENT", message: "sf2Data is required and must be a FlutterStandardTypedData", details: nil))
            return
        }
        let soundfontData = sf2Data.data as Data
        do {
    try soundfontData.write(to: tempFileURL, options: .atomic)
} catch {
    print("Error writing temp file: \(error)")
    return
}
        do {
            audioEngine.attach(samplerNode)
            audioEngine.connect(samplerNode, to: audioEngine.mainMixerNode, format:nil)
            try audioEngine.start()
              // implementing an instrument from a soundfont into a samplerNode
            try samplerNode.loadSoundBankInstrument(at: tempFileURL, program: 0, bankMSB: UInt8(kAUSampler_DefaultMelodicBankMSB), bankLSB: UInt8(kAUSampler_DefaultBankLSB))
        } catch {
            print("Error preparing MIDI: \(error.localizedDescription)")
        }
        result("Soundfont changed successfully")
    case "getInstruments":
        var instruments = [String]()
        for i in 1...16 {
            instruments.append("\(i)")
        }
        result(instruments)
      case "playMidiNote":
        _arguments = call.arguments as! [String : Any];
        let note = UInt8(_arguments["note"] as! Int)
        let velocity = UInt8(_arguments["velocity"] as! Int)
        let channel = UInt8(_arguments["channel"] as! Int)
        samplerNode.startNote(note, withVelocity: velocity, onChannel: channel)
        result(nil)
      case "stopMidiNote":
        _arguments = call.arguments as! [String : Any];
        let note = UInt8(_arguments["note"] as! Int)
        let channel = UInt8(_arguments["channel"] as! Int)
        samplerNode.stopNote(note, onChannel: channel)
        result(nil)
    default:
      result(FlutterMethodNotImplemented)
        break
    }
  }
}