How do you add control transforms and value maps to CoreMIDI.MIDIThruConnectionParams?

I'm trying to create a MIDI Thru connection that filters out specific control messages. I'm coding in Swift on macOS 11 and a Swift beginner.

The MIDIThruConnectionParams structure is variable length. The MIDIControlTransform type is defined but I am not clear how to add them. Is there a recommended / best / safest way?

There is this comment after the structure definition:

// remainder of structure is variable-length:
//      MIDIControlTransform    controls[];
//      MIDIValueMap            maps[];
Answered by OOPer in 657691022
I'm not good at MIDI, so just read Apple's documentations and very few articles on the web.
(This was the only useful one I found.)

One thing sure is that using MIDIThruConnectionParams is very difficult even for experienced Swift programmers.

You may need something like the following:
Code Block
import CoreMIDI
/// Fixed size C-array UInt8[128]
typealias MIDIValueMapValueType = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
extension MIDIValueMap {
init(_ array: [UInt8]) {
assert(array.count == 128, "array needs to contain 128 elements")
let value = array.withUnsafeBytes {bytes in
bytes.load(as: MIDIValueMapValueType.self)
}
self.init(value: value)
}
}
extension UnsafeMutablePointer where Pointee == MIDIThruConnectionParams {
var controls: UnsafeMutablePointer<MIDIControlTransform> {
return (
UnsafeMutableRawPointer(self)
+ MemoryLayout<MIDIThruConnectionParams>.stride
).assumingMemoryBound(to: MIDIControlTransform.self)
}
var maps: UnsafeMutablePointer<MIDIValueMap> {
return (
UnsafeMutableRawPointer(self)
+ MemoryLayout<MIDIThruConnectionParams>.stride
+ MemoryLayout<MIDIControlTransform>.stride * Int(self.pointee.numControlTransforms)
).assumingMemoryBound(to: MIDIValueMap.self)
}
}
func createMIDIThruConnectionParams(
source: MIDIEndpointRef? = nil,
destination: MIDIEndpointRef? = nil,
controlTransforms: [MIDIControlTransform] = [],
maps: [MIDIValueMap] = []
) -> Data {
let size = MemoryLayout<MIDIThruConnectionParams>.stride
+ MemoryLayout<MIDIControlTransform>.stride * controlTransforms.count
+ MemoryLayout<MIDIValueMap>.stride * maps.count
var midiThruConnectionParams = Data(count: size)
midiThruConnectionParams.withUnsafeMutableBytes {bufPtr in
let paramsPtr = bufPtr.baseAddress!.assumingMemoryBound(to: MIDIThruConnectionParams.self)
MIDIThruConnectionParamsInitialize(paramsPtr)
//
if let source = source {
let thruSource = MIDIThruConnectionEndpoint(endpointRef: source, uniqueID: MIDIUniqueID(1))
paramsPtr.pointee.numSources = 1
paramsPtr.pointee.sources.0 = thruSource
}
if let dest = destination {
let thruDest = MIDIThruConnectionEndpoint(endpointRef: dest, uniqueID: MIDIUniqueID(2))
paramsPtr.pointee.numDestinations = 1
paramsPtr.pointee.destinations.0 = thruDest
}
//
paramsPtr.pointee.numControlTransforms = UInt16(controlTransforms.count)
paramsPtr.pointee.numMaps = UInt16(maps.count)
memcpy(paramsPtr.controls, controlTransforms, MemoryLayout<MIDIControlTransform>.stride * controlTransforms.count)
memcpy(paramsPtr.maps, maps, MemoryLayout<MIDIValueMap>.stride * maps.count)
//Do other initializations
//paramsPtr.pointee.... = ...
//...
}
return midiThruConnectionParams
}
let connectionParams = createMIDIThruConnectionParams(
controlTransforms: [
MIDIControlTransform(/*...*/),
//...
]
)
var connectionRef = MIDIThruConnectionRef()
let status = MIDIThruConnectionCreate(nil, connectionParams as CFData, &connectionRef)

(You may need to fix many parts and add more codes...)

a Swift beginner.

If you really are a Swift beginner, I would recommend not to use these structs directly in your Swift code.
Find some third party frameworks that are more Swifty (sorry I do not know if any).
Or learn how to interact with C-based APIs including pointers and C-structs with variable-length member for months, possibly for years.
Accepted Answer
I'm not good at MIDI, so just read Apple's documentations and very few articles on the web.
(This was the only useful one I found.)

One thing sure is that using MIDIThruConnectionParams is very difficult even for experienced Swift programmers.

You may need something like the following:
Code Block
import CoreMIDI
/// Fixed size C-array UInt8[128]
typealias MIDIValueMapValueType = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
extension MIDIValueMap {
init(_ array: [UInt8]) {
assert(array.count == 128, "array needs to contain 128 elements")
let value = array.withUnsafeBytes {bytes in
bytes.load(as: MIDIValueMapValueType.self)
}
self.init(value: value)
}
}
extension UnsafeMutablePointer where Pointee == MIDIThruConnectionParams {
var controls: UnsafeMutablePointer<MIDIControlTransform> {
return (
UnsafeMutableRawPointer(self)
+ MemoryLayout<MIDIThruConnectionParams>.stride
).assumingMemoryBound(to: MIDIControlTransform.self)
}
var maps: UnsafeMutablePointer<MIDIValueMap> {
return (
UnsafeMutableRawPointer(self)
+ MemoryLayout<MIDIThruConnectionParams>.stride
+ MemoryLayout<MIDIControlTransform>.stride * Int(self.pointee.numControlTransforms)
).assumingMemoryBound(to: MIDIValueMap.self)
}
}
func createMIDIThruConnectionParams(
source: MIDIEndpointRef? = nil,
destination: MIDIEndpointRef? = nil,
controlTransforms: [MIDIControlTransform] = [],
maps: [MIDIValueMap] = []
) -> Data {
let size = MemoryLayout<MIDIThruConnectionParams>.stride
+ MemoryLayout<MIDIControlTransform>.stride * controlTransforms.count
+ MemoryLayout<MIDIValueMap>.stride * maps.count
var midiThruConnectionParams = Data(count: size)
midiThruConnectionParams.withUnsafeMutableBytes {bufPtr in
let paramsPtr = bufPtr.baseAddress!.assumingMemoryBound(to: MIDIThruConnectionParams.self)
MIDIThruConnectionParamsInitialize(paramsPtr)
//
if let source = source {
let thruSource = MIDIThruConnectionEndpoint(endpointRef: source, uniqueID: MIDIUniqueID(1))
paramsPtr.pointee.numSources = 1
paramsPtr.pointee.sources.0 = thruSource
}
if let dest = destination {
let thruDest = MIDIThruConnectionEndpoint(endpointRef: dest, uniqueID: MIDIUniqueID(2))
paramsPtr.pointee.numDestinations = 1
paramsPtr.pointee.destinations.0 = thruDest
}
//
paramsPtr.pointee.numControlTransforms = UInt16(controlTransforms.count)
paramsPtr.pointee.numMaps = UInt16(maps.count)
memcpy(paramsPtr.controls, controlTransforms, MemoryLayout<MIDIControlTransform>.stride * controlTransforms.count)
memcpy(paramsPtr.maps, maps, MemoryLayout<MIDIValueMap>.stride * maps.count)
//Do other initializations
//paramsPtr.pointee.... = ...
//...
}
return midiThruConnectionParams
}
let connectionParams = createMIDIThruConnectionParams(
controlTransforms: [
MIDIControlTransform(/*...*/),
//...
]
)
var connectionRef = MIDIThruConnectionRef()
let status = MIDIThruConnectionCreate(nil, connectionParams as CFData, &connectionRef)

(You may need to fix many parts and add more codes...)

a Swift beginner.

If you really are a Swift beginner, I would recommend not to use these structs directly in your Swift code.
Find some third party frameworks that are more Swifty (sorry I do not know if any).
Or learn how to interact with C-based APIs including pointers and C-structs with variable-length member for months, possibly for years.
Thanks OOPer :) Yes, this is certainly a strange way to try and learn Swift.

I am an experienced C programmer. So I'll also take a short look at writing the whole thing in C. Not sure which would be more fun/frustrating.

Thanks for the code ideas. I see some potential there.
This is the simple way - This function connects the first available midi input to first available midi output.

You can set all midi thru params like sources and destinations in this example.


Code Block
    func makeMidiThru() -> OSStatus {
        var connectionRef: MIDIThruConnectionRef = 0
        var params = MIDIThruConnectionParams()
        MIDIThruConnectionParamsInitialize(&params)
params.numSources = 1
        params.numDestinations = 1
        params.sources.0.endpointRef = MIDIGetSource(0)
        params.destinations.0.endpointRef = MIDIGetDestination(0)
        let paramsData = withUnsafePointer(to: params) { p in
            Data(bytes: p, count: MIDIThruConnectionParamsSize(&params))
        }
        return MIDIThruConnectionCreate(nil, paramsData as CFData, &connectionRef)
    }

Hi Tonalscape, not related to this thread. But I have seen on another topic that you also have issues with audio crackling.
I made the test with Ableton, and it also starts crackling if I switch buffer size.
Were you able to solve it? If so, how?
How do you add control transforms and value maps to CoreMIDI.MIDIThruConnectionParams?
 
 
Q