Swift midi connection with external midi controller

94 Views Asked by At

Just beginning coding in Swift with Swift Playgrounds on my iPad. To build my app I first need to know which midi devices are connected and then select the one I want to use. Here is my code. When I press Select, it doesn’t return any name even if I have two devices plugged in.

import SwiftUI
import CoreMIDI

struct ContentView: View {
    @State private var selectedDevice: String = ""
    @State private var deviceList: [String] = []
    
    var body: some View {
        VStack {
            TextField("Selected Device", text: $selectedDevice)
                .padding()
            
            Button("Select Device") {
                self.deviceList = getMIDIDeviceList()
            }
            .padding()
        }
    }
    
    private func getMIDIDeviceList() -> [String] {
        var deviceNames = [String]()
        
        let numberOfDevices = MIDIGetNumberOfDevices()
        for index in 0..<numberOfDevices {
            let device = MIDIGetDevice(index)
            var name: Unmanaged<CFString>?
            MIDIObjectGetStringProperty(device, kMIDIPropertyName, &name)
            if let name = name {
                deviceNames.append(name.takeUnretainedValue() as String)
            }
        }
        
        return deviceNames
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
1

There are 1 best solutions below

0
On

If you click "Select" in your little app, the midi device list is already correctly build, you simply do not display it!

Display your list

You could add this to ContentView:

ForEach(deviceList, id: \.self) { device in
    Text("\(device)")
}

Select from list

But you can also do it more elegantly, by getting the list first with a task modifier when the view is created, then feeding the list to a Picker:

struct ContentView: View {
    @State private var selectedDevice: String = ""
    @State private var devices: [String] = []
    
    var body: some View {
        VStack {
            Text("Selected: \(selectedDevice)")
            Picker("Select Device", selection: $selectedDevice)  {
                ForEach(devices, id: \.self) { device in
                    Text("\(device)")
                }
            }
        }
        .padding()
        .fixedSize()
        .task {
            self.devices = getMIDIDeviceList()
        }
    }
    
    private func getMIDIDeviceList() -> [String] {
        var deviceNames = [String]()
        
        let numberOfDevices = MIDIGetNumberOfDevices()
        for index in 0..<numberOfDevices {
            let device = MIDIGetDevice(index)
            var name: Unmanaged<CFString>?
            MIDIObjectGetStringProperty(device, kMIDIPropertyName, &name)
            if let name = name {
                deviceNames.append(name.takeUnretainedValue() as String)
            }
        }
        return deviceNames
    }
}

enter image description here

Nice, but what about...

Select an actual midi device

You selected a name, but what you will really need is selecting a device. For this you will need MIDIDeviceRef again and again. We need to store this somewhere, so we create a mini-model for the deviced called MidiDevice, and feed the picker with the changed list:

import SwiftUI
import CoreMIDI

struct MidiDevice: Hashable, Identifiable {
    let id: MIDIDeviceRef
    let name: String?
}

extension MidiDevice: CustomStringConvertible {
    var description: String {
        self.name ?? "MidiDevice_\(self.id)"
    }
}

struct ContentView: View {
    @State private var selectedDevice: MidiDevice?
    @State private var devices: [MidiDevice] = []
    
    var body: some View {
        VStack {
            Text("Selected: \(selectedDevice?.description ?? "- none -")")
            Picker("Select Device", selection: $selectedDevice)  {
                ForEach(devices) { device in
                    Text("\(device.description)").tag(device as MidiDevice?)
                }
            }
        }
        .padding()
        .fixedSize()
        .task {
            self.devices = getMIDIDeviceList()
        }
    }
    
    private func getMIDIDeviceList() -> [MidiDevice] {
        var devices: [MidiDevice] = []
        
        let numberOfDevices = MIDIGetNumberOfDevices()
        for index in 0..<numberOfDevices {
            let midiRef: MIDIDeviceRef = MIDIGetDevice(index)
            var name: Unmanaged<CFString>?
            MIDIObjectGetStringProperty(midiRef, kMIDIPropertyName, &name)
            devices.append(MidiDevice(id: midiRef, name: name?.takeUnretainedValue() as String?))
        }
        return devices
    }
}