diff --git a/Sources/MIDIKit/IO/Objects/Endpoint/AnyEndpoint/AnyEndpoint.swift b/Sources/MIDIKit/IO/Objects/Endpoint/AnyEndpoint/AnyEndpoint.swift new file mode 100644 index 0000000000..95f6646e38 --- /dev/null +++ b/Sources/MIDIKit/IO/Objects/Endpoint/AnyEndpoint/AnyEndpoint.swift @@ -0,0 +1,49 @@ +// +// AnyEndpoint.swift +// MIDIKit • https://github.com/orchetect/MIDIKit +// + +import CoreMIDI + +extension MIDI.IO { + + /// Type-erased box that can contain `MIDI.IO.InputEndpoint` or `MIDI.IO.OutputEndpoint`. + public struct AnyEndpoint: MIDIIOEndpointProtocol { + + public let endpointType: MIDI.IO.EndpointType + + public let coreMIDIObjectRef: MIDIEndpointRef + + public let name: String + + public var uniqueID: UniqueID + + internal init(_ other: T) { + switch other { + case is MIDI.IO.InputEndpoint: + endpointType = .input + + case is MIDI.IO.OutputEndpoint: + endpointType = .output + + case let otherCast as Self: + endpointType = otherCast.endpointType + + default: + preconditionFailure("Unexpected MIDIIOEndpointProtocol type: \(other)") + } + + self.coreMIDIObjectRef = other.coreMIDIObjectRef + self.name = other.name + self.uniqueID = .init(other.uniqueID.coreMIDIUniqueID) + } + + } + +} + +extension MIDI.IO.AnyEndpoint { + + public typealias UniqueID = MIDI.IO.AnyUniqueID + +} diff --git a/Sources/MIDIKit/IO/Objects/Endpoint/EndpointType.swift b/Sources/MIDIKit/IO/Objects/Endpoint/EndpointType.swift new file mode 100644 index 0000000000..e4397e8f34 --- /dev/null +++ b/Sources/MIDIKit/IO/Objects/Endpoint/EndpointType.swift @@ -0,0 +1,13 @@ +// +// EndpointType.swift +// MIDIKit • https://github.com/orchetect/MIDIKit +// + +extension MIDI.IO { + + public enum EndpointType: Equatable, Hashable, CaseIterable { + case input + case output + } + +} diff --git a/Sources/MIDIKit/IO/Objects/Endpoint/InputEndpoint/InputEndpoint.swift b/Sources/MIDIKit/IO/Objects/Endpoint/InputEndpoint/InputEndpoint.swift index b3567dc81d..eedcc4670c 100644 --- a/Sources/MIDIKit/IO/Objects/Endpoint/InputEndpoint/InputEndpoint.swift +++ b/Sources/MIDIKit/IO/Objects/Endpoint/InputEndpoint/InputEndpoint.swift @@ -33,12 +33,8 @@ extension MIDI.IO { // MARK: - Properties (Cached) - /// User-visible endpoint name. - /// (`kMIDIPropertyName`) public internal(set) var name: String = "" - /// System-global Unique ID. - /// (`kMIDIPropertyUniqueID`) public internal(set) var uniqueID: UniqueID = 0 /// Update the cached properties @@ -76,6 +72,14 @@ extension MIDI.IO.InputEndpoint { } +extension MIDI.IO.InputEndpoint { + + /// Returns the endpoint as a type-erased `AnyEndpoint`. + public var asAnyEndpoint: MIDI.IO.AnyEndpoint { + .init(self) + } + +} extension MIDI.IO.InputEndpoint: CustomDebugStringConvertible { diff --git a/Sources/MIDIKit/IO/Objects/Endpoint/MIDIIOEndpointProtocol.swift b/Sources/MIDIKit/IO/Objects/Endpoint/MIDIIOEndpointProtocol.swift index 21ce9afc61..88560b9169 100644 --- a/Sources/MIDIKit/IO/Objects/Endpoint/MIDIIOEndpointProtocol.swift +++ b/Sources/MIDIKit/IO/Objects/Endpoint/MIDIIOEndpointProtocol.swift @@ -23,3 +23,14 @@ extension MIDIIOEndpointProtocol { } } + +extension Collection where Element : MIDIIOEndpointProtocol { + + /// Returns the collection as a collection of type-erased `AnyEndpoint` endpoints. + public var asAnyEndpoints: [MIDI.IO.AnyEndpoint] { + + map { MIDI.IO.AnyEndpoint($0) } + + } + +} diff --git a/Sources/MIDIKit/IO/Objects/Endpoint/OutputEndpoint/OutputEndpoint.swift b/Sources/MIDIKit/IO/Objects/Endpoint/OutputEndpoint/OutputEndpoint.swift index 58fe0c9d21..db40388867 100644 --- a/Sources/MIDIKit/IO/Objects/Endpoint/OutputEndpoint/OutputEndpoint.swift +++ b/Sources/MIDIKit/IO/Objects/Endpoint/OutputEndpoint/OutputEndpoint.swift @@ -33,12 +33,8 @@ extension MIDI.IO { // MARK: - Properties (Cached) - /// User-visible endpoint name. - /// (`kMIDIPropertyName`) public internal(set) var name: String = "" - /// System-global Unique ID. - /// (`kMIDIPropertyUniqueID`) public internal(set) var uniqueID: UniqueID = 0 /// Update the cached properties @@ -76,6 +72,15 @@ extension MIDI.IO.OutputEndpoint { } +extension MIDI.IO.OutputEndpoint { + + /// Returns the endpoint as a type-erased `AnyEndpoint`. + public var asAnyEndpoint: MIDI.IO.AnyEndpoint { + .init(self) + } + +} + extension MIDI.IO.OutputEndpoint: CustomDebugStringConvertible { public var debugDescription: String { diff --git a/Sources/MIDIKit/IO/UniqueID/AnyUniqueID.swift b/Sources/MIDIKit/IO/UniqueID/AnyUniqueID.swift new file mode 100644 index 0000000000..877ad7f995 --- /dev/null +++ b/Sources/MIDIKit/IO/UniqueID/AnyUniqueID.swift @@ -0,0 +1,70 @@ +// +// AnyUniqueID.swift +// MIDIKit • https://github.com/orchetect/MIDIKit +// + +import CoreMIDI + +extension MIDI.IO { + + /// Type-erased box for `MIDIIOUniqueIDProtocol`. + /// + /// MIDIKit Object Unique ID value type. + /// Analogous with CoreMIDI value of `MIDIObjectRef` property key `kMIDIPropertyUniqueID`. + public struct AnyUniqueID: MIDIIOUniqueIDProtocol { + + public let coreMIDIUniqueID: MIDIUniqueID + + public init(_ coreMIDIUniqueID: MIDIUniqueID) { + self.coreMIDIUniqueID = coreMIDIUniqueID + } + + } + +} + +extension MIDI.IO.AnyEndpoint.UniqueID: Equatable { + // default implementation provided by MIDIIOUniqueIDProtocol +} + +extension MIDI.IO.AnyEndpoint.UniqueID: Hashable { + // default implementation provided by MIDIIOUniqueIDProtocol +} + +extension MIDI.IO.AnyEndpoint.UniqueID: Identifiable { + // default implementation provided by MIDIIOUniqueIDProtocol +} + +extension MIDI.IO.AnyEndpoint.UniqueID: ExpressibleByIntegerLiteral { + + public typealias IntegerLiteralType = MIDIUniqueID + + public init(integerLiteral value: IntegerLiteralType) { + + coreMIDIUniqueID = value + + } + +} + +extension MIDI.IO.AnyEndpoint.UniqueID: CustomStringConvertible { + + public var description: String { + + return "\(coreMIDIUniqueID)" + + } + +} + +extension MIDIIOUniqueIDProtocol { + + /// Returns the unique ID as a type-erased `AnyUniqueID`. + public var asAnyUniqueID: MIDI.IO.AnyUniqueID { + + .init(coreMIDIUniqueID) + + } + +} + diff --git a/Sources/MIDIKit/IO/UniqueID/MIDIIOUniqueIDProtocol.swift b/Sources/MIDIKit/IO/UniqueID/MIDIIOUniqueIDProtocol.swift index 03c2b51a9b..7ac45b1e74 100644 --- a/Sources/MIDIKit/IO/UniqueID/MIDIIOUniqueIDProtocol.swift +++ b/Sources/MIDIKit/IO/UniqueID/MIDIIOUniqueIDProtocol.swift @@ -16,6 +16,17 @@ public protocol MIDIIOUniqueIDProtocol { } +extension Collection where Element == MIDIIOUniqueIDProtocol { + + /// Returns the collection as a collection of type-erased `AnyUniqueID` unique IDs. + public var asAnyUniqueIDs: [MIDI.IO.AnyUniqueID] { + + map { MIDI.IO.AnyUniqueID($0.coreMIDIUniqueID) } + + } + +} + // MARK: - MIDIIOEndpointUniqueIDProtocol public protocol MIDIIOEndpointUniqueIDProtocol: MIDIIOUniqueIDProtocol { diff --git a/Tests/MIDIKitTests/IO/MIDIIO/Errors/MIDIOSStatus Tests.swift b/Tests/MIDIKitTests/IO/Errors/MIDIOSStatus Tests.swift similarity index 100% rename from Tests/MIDIKitTests/IO/MIDIIO/Errors/MIDIOSStatus Tests.swift rename to Tests/MIDIKitTests/IO/Errors/MIDIOSStatus Tests.swift diff --git a/Tests/MIDIKitTests/IO/MIDIIO/Inputs and Outputs/Input Tests.swift b/Tests/MIDIKitTests/IO/Inputs and Outputs/Input Tests.swift similarity index 100% rename from Tests/MIDIKitTests/IO/MIDIIO/Inputs and Outputs/Input Tests.swift rename to Tests/MIDIKitTests/IO/Inputs and Outputs/Input Tests.swift diff --git a/Tests/MIDIKitTests/IO/MIDIIO/Inputs and Outputs/InputConnection Tests.swift b/Tests/MIDIKitTests/IO/Inputs and Outputs/InputConnection Tests.swift similarity index 100% rename from Tests/MIDIKitTests/IO/MIDIIO/Inputs and Outputs/InputConnection Tests.swift rename to Tests/MIDIKitTests/IO/Inputs and Outputs/InputConnection Tests.swift diff --git a/Tests/MIDIKitTests/IO/MIDIIO/Inputs and Outputs/Output Tests.swift b/Tests/MIDIKitTests/IO/Inputs and Outputs/Output Tests.swift similarity index 100% rename from Tests/MIDIKitTests/IO/MIDIIO/Inputs and Outputs/Output Tests.swift rename to Tests/MIDIKitTests/IO/Inputs and Outputs/Output Tests.swift diff --git a/Tests/MIDIKitTests/IO/MIDIIO/Inputs and Outputs/OutputConnection Tests.swift b/Tests/MIDIKitTests/IO/Inputs and Outputs/OutputConnection Tests.swift similarity index 100% rename from Tests/MIDIKitTests/IO/MIDIIO/Inputs and Outputs/OutputConnection Tests.swift rename to Tests/MIDIKitTests/IO/Inputs and Outputs/OutputConnection Tests.swift diff --git a/Tests/MIDIKitTests/IO/MIDIIO/Inputs and Outputs/ThruConnection Tests.swift b/Tests/MIDIKitTests/IO/Inputs and Outputs/ThruConnection Tests.swift similarity index 100% rename from Tests/MIDIKitTests/IO/MIDIIO/Inputs and Outputs/ThruConnection Tests.swift rename to Tests/MIDIKitTests/IO/Inputs and Outputs/ThruConnection Tests.swift diff --git a/Tests/MIDIKitTests/IO/MIDIIO/Manager/Manager Tests.swift b/Tests/MIDIKitTests/IO/Manager/Manager Tests.swift similarity index 92% rename from Tests/MIDIKitTests/IO/MIDIIO/Manager/Manager Tests.swift rename to Tests/MIDIKitTests/IO/Manager/Manager Tests.swift index 5c4ce78b12..6c0bd6be50 100644 --- a/Tests/MIDIKitTests/IO/MIDIIO/Manager/Manager Tests.swift +++ b/Tests/MIDIKitTests/IO/Manager/Manager Tests.swift @@ -25,6 +25,8 @@ final class Manager_Tests: XCTestCase { func testMIDIO_Manager_defaults() { + // just check defaults without calling .start() on the manager + XCTAssertEqual(manager.clientName, "MIDIKit_IO_Manager_Tests") XCTAssertEqual(manager.clientRef, MIDIClientRef()) diff --git a/Tests/MIDIKitTests/IO/Objects/Endpoint/AnyEndpoint Tests.swift b/Tests/MIDIKitTests/IO/Objects/Endpoint/AnyEndpoint Tests.swift new file mode 100644 index 0000000000..b0f49cd810 --- /dev/null +++ b/Tests/MIDIKitTests/IO/Objects/Endpoint/AnyEndpoint Tests.swift @@ -0,0 +1,101 @@ +// +// AnyEndpoint Tests.swift +// MIDIKit • https://github.com/orchetect/MIDIKit +// + +// iOS Simulator XCTest testing does not give enough permissions to allow creating virtual MIDI ports, so skip these tests on iOS targets +#if !os(watchOS) && !targetEnvironment(simulator) + +import XCTest +@testable import MIDIKit + +class AnyEndpoint_Tests: XCTestCase { + + var manager: MIDI.IO.Manager! = nil + + override func setUp() { + manager = .init(clientName: "MIDIKit_IO_AnyEndpoint_Tests", + model: "", + manufacturer: "") + + guard let _ = try? manager.start() else { + XCTFail("Couldn't start MIDI.IO.Manager.") + return + } + } + + override func tearDown() { + manager = nil + } + + func testAnyEndpoint() throws { + + // to properly test this, we need to actually + // create a couple MIDI endpoints in the system first + + let kInputName = "MIDIKit Test Input" + let kInputTag = "testInput" + try manager.addInput(name: kInputName, + tag: kInputTag, + uniqueID: .none, + receiveHandler: .rawDataLogging()) + + let kOutputName = "MIDIKit Test Output" + let kOutputTag = "testOutput" + try manager.addOutput(name: kOutputName, + tag: kOutputTag, + uniqueID: .none) + + // have to give CoreMIDI a bit of time to create the ports (async) + XCTWait(sec: 1.0) + + // input + + guard let inputUniqueID = manager.managedInputs[kInputTag]?.uniqueID, + let inputEndpoint = manager.endpoints.inputs.first(whereUniqueID: inputUniqueID) + else { XCTFail() ; return } + + let anyInput = MIDI.IO.AnyEndpoint(inputEndpoint) + _ = inputEndpoint.asAnyEndpoint // also works + XCTAssertEqual(anyInput.coreMIDIObjectRef, inputEndpoint.coreMIDIObjectRef) + XCTAssertEqual(anyInput.name, kInputName) + XCTAssertEqual(anyInput.endpointType, .input) + + // output + + guard let outputUniqueID = manager.managedOutputs[kOutputTag]?.uniqueID, + let outputEndpoint = manager.endpoints.outputs.first(whereUniqueID: outputUniqueID) + else { XCTFail() ; return } + + let anyOutput = MIDI.IO.AnyEndpoint(outputEndpoint) + _ = outputEndpoint.asAnyEndpoint // also works + XCTAssertEqual(outputEndpoint.coreMIDIObjectRef, anyOutput.coreMIDIObjectRef) + XCTAssertEqual(anyOutput.name, kOutputName) + XCTAssertEqual(anyOutput.endpointType, .output) + + // Collection + + let inputArray = [inputEndpoint] + let inputArrayAsAnyEndpoints = inputArray.asAnyEndpoints + XCTAssertEqual(inputArrayAsAnyEndpoints.count, 1) + XCTAssertEqual(inputArrayAsAnyEndpoints[0].coreMIDIObjectRef, inputEndpoint.coreMIDIObjectRef) + + let outputArray = [outputEndpoint] + let outputArrayAsAnyEndpoints = outputArray.asAnyEndpoints + XCTAssertEqual(outputArrayAsAnyEndpoints.count, 1) + XCTAssertEqual(outputArrayAsAnyEndpoints[0].coreMIDIObjectRef, outputEndpoint.coreMIDIObjectRef) + + let combined = inputArrayAsAnyEndpoints + outputArrayAsAnyEndpoints + XCTAssertEqual(combined.count, 2) + + // AnyEndpoint from AnyEndpoint (just to check for crashes) + let anyAny = MIDI.IO.AnyEndpoint(anyInput) + XCTAssertEqual(anyAny.coreMIDIObjectRef, anyInput.coreMIDIObjectRef) + XCTAssertEqual(anyAny.name, anyInput.name) + XCTAssertEqual(anyAny.endpointType, .input) + XCTAssertEqual(anyAny.uniqueID, anyInput.uniqueID) + + } + +} +#endif diff --git a/Tests/MIDIKitTests/IO/UniqueID/AnyUniqueID Tests.swift b/Tests/MIDIKitTests/IO/UniqueID/AnyUniqueID Tests.swift new file mode 100644 index 0000000000..c0b4824801 --- /dev/null +++ b/Tests/MIDIKitTests/IO/UniqueID/AnyUniqueID Tests.swift @@ -0,0 +1,33 @@ +// +// AnyUniqueID Tests.swift +// MIDIKit • https://github.com/orchetect/MIDIKit +// + +#if !os(watchOS) + +import XCTest +@testable import MIDIKit + +class AnyUniqueID_Tests: XCTestCase { + + func testAnyUniqueID() { + + let allUniqueIDTypes: [MIDIIOUniqueIDProtocol] = [ + MIDI.IO.Device.UniqueID(10000000), + MIDI.IO.Entity.UniqueID(10000001), + MIDI.IO.InputEndpoint.UniqueID(10000002), + MIDI.IO.OutputEndpoint.UniqueID(10000003) + ] + + let anyArray = allUniqueIDTypes.asAnyUniqueIDs + + XCTAssertEqual(anyArray.count, 4) + XCTAssertEqual(anyArray[0].coreMIDIUniqueID, 10000000) + XCTAssertEqual(anyArray[1].coreMIDIUniqueID, 10000001) + XCTAssertEqual(anyArray[2].coreMIDIUniqueID, 10000002) + XCTAssertEqual(anyArray[3].coreMIDIUniqueID, 10000003) + + } + +} +#endif