Swift midi connection with external midi controller

142 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
soundflix 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
    }
}