diff --git a/Examples/MIDIEventLogger/MIDIEventLogger/AppDelegate.swift b/Examples/MIDIEventLogger/MIDIEventLogger/AppDelegate.swift index e0f455e9e4..4507891c48 100644 --- a/Examples/MIDIEventLogger/MIDIEventLogger/AppDelegate.swift +++ b/Examples/MIDIEventLogger/MIDIEventLogger/AppDelegate.swift @@ -23,8 +23,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { logger.default(error) } -// #warning("> TODO: remove this") -// newManager.preferredAPI = .legacyCoreMIDI + // uncomment this to test different API versions or limit to MIDI 1.0 protocol + //newManager.preferredAPI = .legacyCoreMIDI return newManager }() diff --git a/Examples/MIDIEventLogger/MIDIEventLogger/ContentView SubViews.swift b/Examples/MIDIEventLogger/MIDIEventLogger/ContentView SubViews.swift index 052b63edbb..c6cdf6db27 100644 --- a/Examples/MIDIEventLogger/MIDIEventLogger/ContentView SubViews.swift +++ b/Examples/MIDIEventLogger/MIDIEventLogger/ContentView SubViews.swift @@ -67,32 +67,34 @@ extension ContentView { HStack(alignment: .top) { GroupBox(label: Text("Channel Voice")) { - Picker("Channel", selection: $midiChannel) { - ForEach(0..<15+1, id: \.self) { - let channelNum = $0 + 1 - let channelNumHex = $0.hex.stringValue(padTo: 1, prefix: true) - - Text("\(channelNum) (\(channelNumHex))") - .tag(MIDI.UInt4($0)) - } - } - .frame(maxWidth: 200) - - Spacer() - .frame(height: 10) - - HStack { - SendMIDIEventsChannelVoiceView(midiGroup: $midiGroup, - midiChannel: $midiChannel) { - sendEvent($0) + VStack { + Picker("Channel", selection: $midiChannel) { + ForEach(0..<15+1, id: \.self) { + let channelNum = $0 + 1 + let channelNumHex = $0.hex.stringValue(padTo: 1, prefix: true) + + Text("\(channelNum) (\(channelNumHex))") + .tag(MIDI.UInt4($0)) + } } - SendMIDIEventsMIDI2ChannelVoiceView(midiGroup: $midiGroup, - midiChannel: $midiChannel) { - sendEvent($0) + .frame(maxWidth: 200) + + Spacer() + .frame(height: 10) + + HStack { + SendMIDIEventsChannelVoiceView(midiGroup: $midiGroup, + midiChannel: $midiChannel) { + sendEvent($0) + } + SendMIDIEventsMIDI2ChannelVoiceView(midiGroup: $midiGroup, + midiChannel: $midiChannel) { + sendEvent($0) + } } } + .frame(width: 280 + 280 + 40, height: 270) } - .frame(width: 280 + 280 + 40, height: 270) SendMIDIEventsSystemExclusiveView(midiGroup: $midiGroup) { sendEvent($0) @@ -220,7 +222,7 @@ extension ContentView { var body: some View { - GroupBox(label: Text("MIDI 2.0 Only")) { + GroupBox(label: Text("MIDI 2.0")) { VStack(alignment: .center, spacing: 8) { @@ -246,7 +248,7 @@ extension ContentView { value: 0x8000_0000, // UInt32 "midpoint" value channel: midiChannel, group: midiGroup)) - } label: { Text("Note CC (Registered)") } + } label: { Text("Per-Note CC (Registered)") } Button { sendEvent(.noteCC(note: 60, @@ -254,14 +256,14 @@ extension ContentView { value: 0x8000_0000, // UInt32 "midpoint" value channel: midiChannel, group: midiGroup)) - } label: { Text("Note CC (Assignable)") } + } label: { Text("Per-Note CC (Assignable)") } Button { sendEvent(.notePitchBend(note: 60, value: .midi2(0x8000_0000), channel: midiChannel, group: midiGroup)) - } label: { Text("Note PitchBend") } + } label: { Text("Per-Note PitchBend") } HStack { Button { @@ -315,75 +317,173 @@ extension ContentView { VStack(alignment: .center, spacing: 8) { - Button { - sendEvent(.sysEx7(manufacturer: .educational(), - data: [], - group: midiGroup)) - } label: { Text("SysEx7 (0 Data, 1 Total)") } - - Button { - sendEvent(.sysEx7(manufacturer: .educational(), - data: [0x01, 0x02], - group: midiGroup)) - } label: { Text("SysEx7 (2 Data, 3 Total)") } - - Button { - sendEvent(.sysEx7(manufacturer: .educational(), - data: [0x01, 0x02, 0x03, 0x04, 0x05], - group: midiGroup)) - } label: { Text("SysEx7 (5 Data, 6 Total)") } - - Button { - sendEvent(.sysEx7(manufacturer: .educational(), - data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07], - group: midiGroup)) - } label: { Text("SysEx7 (7 Data, 8 Total)") } - - Button { - sendEvent(.sysEx7(manufacturer: .educational(), - data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, - 0x0D], - group: midiGroup)) - } label: { Text("SysEx7 (13 Data, 14 Total)") } - - Button { - sendEvent(.universalSysEx7(universalType: .realTime, - deviceID: 0x7F, - subID1: 0x7F, - subID2: 0x7F, - data: [], - group: midiGroup)) - } label: { Text("Universal SysEx7 (0 Data, 4 Total)") } + GroupBox(label: Text("SysEx7")) { + HStack { + Text("Bytes:") + + Button { + sendEvent(.sysEx7(manufacturer: .educational(), + data: [], + group: midiGroup)) + } label: { Text("0") } + + Button { + sendEvent(.sysEx7(manufacturer: .educational(), + data: [0x01, 0x02], + group: midiGroup)) + } label: { Text("2") } + + Button { + sendEvent(.sysEx7(manufacturer: .educational(), + data: [0x01, 0x02, 0x03, 0x04, 0x05], + group: midiGroup)) + } label: { Text("5") } + + Button { + sendEvent(.sysEx7(manufacturer: .educational(), + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07], + group: midiGroup)) + } label: { Text("7") } + + Button { + sendEvent(.sysEx7(manufacturer: .educational(), + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + 0x0D], + group: midiGroup)) + } label: { Text("13") } + } + .frame(width: 220) + } - Button { - sendEvent(.universalSysEx7(universalType: .nonRealTime, - deviceID: 0x7F, - subID1: 0x7F, - subID2: 0x7F, - data: [0x01, 0x02], - group: midiGroup)) - } label: { Text("Universal SysEx7 (2 Data, 6 Total)") } + GroupBox(label: Text("Universal SysEx7")) { + HStack { + Text("Bytes:") + + Button { + sendEvent(.universalSysEx7(universalType: .realTime, + deviceID: 0x7F, + subID1: 0x7F, + subID2: 0x7F, + data: [], + group: midiGroup)) + } label: { Text("0") } + + Button { + sendEvent(.universalSysEx7(universalType: .nonRealTime, + deviceID: 0x7F, + subID1: 0x7F, + subID2: 0x7F, + data: [0x01, 0x02], + group: midiGroup)) + } label: { Text("2") } + + Button { + sendEvent(.universalSysEx7(universalType: .realTime, + deviceID: 0x7F, + subID1: 0x7F, + subID2: 0x7F, + data: [0x01, 0x02, 0x03, 0x04], + group: midiGroup)) + } label: { Text("4") } + + Button { + sendEvent(.universalSysEx7(universalType: .nonRealTime, + deviceID: 0x7F, + subID1: 0x7F, + subID2: 0x7F, + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A], + group: midiGroup)) + } label: { Text("10") } + } + .frame(width: 220) + } - Button { - sendEvent(.universalSysEx7(universalType: .realTime, - deviceID: 0x7F, - subID1: 0x7F, - subID2: 0x7F, - data: [0x01, 0x02, 0x03, 0x04], - group: midiGroup)) - } label: { Text("Universal SysEx7 (4 Data, 8 Total)") } + GroupBox(label: Text("SysEx8 (MIDI 2.0)")) { + HStack { + Text("Bytes:") + + Button { + sendEvent(.sysEx8(manufacturer: .educational(), + data: [], + group: midiGroup)) + } label: { Text("0") } + + Button { + sendEvent(.sysEx8(manufacturer: .educational(), + data: [0x01, 0xE0], + group: midiGroup)) + } label: { Text("2") } + + Button { + sendEvent(.sysEx8(manufacturer: .educational(), + data: [0x01, 0x02, 0x03, 0xE0, 0xE1], + group: midiGroup)) + } label: { Text("5") } + + Button { + sendEvent(.sysEx8(manufacturer: .educational(), + data: [0x01, 0x02, 0x03, 0x04, 0xE0, 0xE1, + 0xE2], + group: midiGroup)) + } label: { Text("7") } + + Button { + sendEvent(.sysEx8(manufacturer: .educational(), + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, + 0xE6], + group: midiGroup)) + } label: { Text("13") } + } + .frame(width: 220) + } - Button { - sendEvent(.universalSysEx7(universalType: .nonRealTime, - deviceID: 0x7F, - subID1: 0x7F, - subID2: 0x7F, - data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0A], - group: midiGroup)) - } label: { Text("Universal SysEx7 (10 Data, 14 Total)") } + GroupBox(label: Text("Universal SysEx8 (MIDI 2.0)")) { + HStack { + Text("Bytes:") + + Button { + sendEvent(.universalSysEx8(universalType: .realTime, + deviceID: 0x7F, + subID1: 0x7F, + subID2: 0x7F, + data: [], + group: midiGroup)) + } label: { Text("0") } + + Button { + sendEvent(.universalSysEx8(universalType: .nonRealTime, + deviceID: 0x7F, + subID1: 0x7F, + subID2: 0x7F, + data: [0x01, 0xE0], + group: midiGroup)) + } label: { Text("2") } + + Button { + sendEvent(.universalSysEx8(universalType: .realTime, + deviceID: 0x7F, + subID1: 0x7F, + subID2: 0x7F, + data: [0x01, 0x02, 0xE0, 0xE1], + group: midiGroup)) + } label: { Text("4") } + + Button { + sendEvent(.universalSysEx8(universalType: .nonRealTime, + deviceID: 0x7F, + subID1: 0x7F, + subID2: 0x7F, + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0xE0, 0xE1, 0xF1, 0xF2], + group: midiGroup)) + } label: { Text("10") } + } + .frame(width: 220) + } } .padding() diff --git a/Sources/MIDIKit/Events/Event/Event description.swift b/Sources/MIDIKit/Events/Event/Event description.swift index ccf9f88a00..569f27dd13 100644 --- a/Sources/MIDIKit/Events/Event/Event description.swift +++ b/Sources/MIDIKit/Events/Event/Event description.swift @@ -4,6 +4,7 @@ // import Foundation +@_implementationOnly import SwiftRadix extension MIDI.Event: CustomStringConvertible, CustomDebugStringConvertible { @@ -24,7 +25,10 @@ extension MIDI.Event: CustomStringConvertible, CustomDebugStringConvertible { attrStr = "\(event.attribute), " } - return "noteOn(\(event.note), vel: \(event.velocity), \(attrStr)chan: \(event.channel), group: \(event.group))" + let channelString = event.channel.value.hex.stringValue(prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) + + return "noteOn(\(event.note), vel: \(event.velocity), \(attrStr)chan: \(channelString), group: \(groupString))" case .noteOff(let event): let attrStr: String @@ -35,44 +39,63 @@ extension MIDI.Event: CustomStringConvertible, CustomDebugStringConvertible { attrStr = "\(event.attribute), " } - return "noteOff(\(event.note), vel: \(event.velocity), \(attrStr)chan: \(event.channel), group: \(event.group))" + let channelString = event.channel.value.hex.stringValue(prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) + + return "noteOff(\(event.note), vel: \(event.velocity), \(attrStr)chan: \(channelString), group: \(groupString))" case .noteCC(let event): + let channelString = event.channel.value.hex.stringValue(prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - return "noteCC(note: \(event.note), controller: \(event.controller), val: \(event.value), chan: \(event.channel), group: \(event.group))" + return "noteCC(note: \(event.note), controller: \(event.controller), val: \(event.value), chan: \(channelString), group: \(groupString))" case .notePitchBend(let event): + let channelString = event.channel.value.hex.stringValue(prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - return "notePitchBend(note: \(event.note), value: \(event.value), chan: \(event.channel), group: \(event.group))" + return "notePitchBend(note: \(event.note), value: \(event.value), chan: \(channelString), group: \(groupString))" case .notePressure(let event): + let channelString = event.channel.value.hex.stringValue(prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - return "notePressure(note:\(event.note), amount: \(event.amount), chan: \(event.channel), group: \(event.group))" + return "notePressure(note:\(event.note), amount: \(event.amount), chan: \(channelString), group: \(groupString))" case .noteManagement(let event): + let channelString = event.channel.value.hex.stringValue(prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - return "noteManagement(options: \(event.optionFlags), chan: \(event.channel), group: \(event.group))" + return "noteManagement(options: \(event.optionFlags), chan: \(channelString), group: \(groupString))" case .cc(let event): + let channelString = event.channel.value.hex.stringValue(prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - return "cc(\(event.controller.number), val: \(event.value), chan: \(event.channel), group: \(event.group))" + return "cc(\(event.controller.number), val: \(event.value), chan: \(channelString), group: \(groupString))" case .programChange(let event): + let channelString = event.channel.value.hex.stringValue(prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) switch event.bank { case .noBankSelect: - return "prgChange(\(event.program), chan: \(event.channel), group: \(event.group))" + return "prgChange(\(event.program), chan: \(channelString), group: \(groupString))" case .bankSelect(let bank): - return "prgChange(\(event.program), bank: \(bank), chan: \(event.channel), group: \(event.group))" + return "prgChange(\(event.program), bank: \(bank), chan: \(channelString), group: \(groupString))" } case .pressure(let event): + let channelString = event.channel.value.hex.stringValue(prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - return "pressure(amount: \(event.amount), chan: \(event.channel), group: \(event.group))" + return "pressure(amount: \(event.amount), chan: \(channelString), group: \(groupString))" case .pitchBend(let event): + let channelString = event.channel.value.hex.stringValue(prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - return "pitchBend(\(event.value), chan: \(event.channel), group: \(event.group))" + return "pitchBend(\(event.value), chan: \(channelString), group: \(groupString))" // ---------------------- @@ -80,32 +103,28 @@ extension MIDI.Event: CustomStringConvertible, CustomDebugStringConvertible { // ---------------------- case .sysEx7(let event): + let dataString = event.data.hex.stringValue(padTo: 2, prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - let dataString = event.data - .hex.stringValue(padTo: 2, prefix: true) - - return "sysEx7(mfr: \(event.manufacturer), data: [\(dataString)], group: \(event.group))" + return "sysEx7(mfr: \(event.manufacturer), data: [\(dataString)], group: \(groupString))" case .universalSysEx7(let event): + let dataString = event.data.hex.stringValue(padTo: 2, prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - let dataString = event.data - .hex.stringValue(padTo: 2, prefix: true) - - return "universalSysEx7(\(event.universalType), deviceID: \(event.deviceID), subID1: \(event.subID1), subID2: \(event.subID2), data: [\(dataString)], group: \(event.group))" + return "universalSysEx7(\(event.universalType), deviceID: \(event.deviceID), subID1: \(event.subID1), subID2: \(event.subID2), data: [\(dataString)], group: \(groupString))" case .sysEx8(let event): + let dataString = event.data.hex.stringValue(padTo: 2, prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - let dataString = event.data - .hex.stringValue(padTo: 2, prefix: true) - - return "sysEx8(mfr: \(event.manufacturer), data: [\(dataString)], group: \(event.group), streamID: \(event.streamID))" + return "sysEx8(mfr: \(event.manufacturer), data: [\(dataString)], group: \(groupString), streamID: \(event.streamID))" case .universalSysEx8(let event): + let dataString = event.data.hex.stringValue(padTo: 2, prefix: true) + let groupString = event.group.value.hex.stringValue(prefix: true) - let dataString = event.data - .hex.stringValue(padTo: 2, prefix: true) - - return "universalSysEx8(\(event.universalType), deviceID: \(event.deviceID), subID1: \(event.subID1), subID2: \(event.subID2), data: [\(dataString)], group: \(event.group), streamID: \(event.streamID))" + return "universalSysEx8(\(event.universalType), deviceID: \(event.deviceID), subID1: \(event.subID1), subID2: \(event.subID2), data: [\(dataString)], group: \(groupString), streamID: \(event.streamID))" // ------------------- @@ -113,27 +132,28 @@ extension MIDI.Event: CustomStringConvertible, CustomDebugStringConvertible { // ------------------- case .timecodeQuarterFrame(let event): - let dataByteString = event.dataByte.uInt8Value .binary.stringValue(padTo: 8, splitEvery: 8, prefix: true) - return "timecodeQF(\(dataByteString), group: \(event.group))" + let groupString = event.group.value.hex.stringValue(prefix: true) - case .songPositionPointer(let event): + return "timecodeQF(\(dataByteString), group: \(groupString))" - return "songPositionPointer(beat: \(event.midiBeat), group: \(event.group))" + case .songPositionPointer(let event): + let groupString = event.group.value.hex.stringValue(prefix: true) + return "songPositionPointer(beat: \(event.midiBeat), group: \(groupString))" case .songSelect(let event): - - return "songSelect(number: \(event.number), group: \(event.group))" + let groupString = event.group.value.hex.stringValue(prefix: true) + return "songSelect(number: \(event.number), group: \(groupString))" case .unofficialBusSelect(let event): - - return "unofficialBusSelect(bus: \(event.bus), group: \(event.group))" + let groupString = event.group.value.hex.stringValue(prefix: true) + return "unofficialBusSelect(bus: \(event.bus), group: \(groupString))" case .tuneRequest(let event): - - return "tuneRequest(group: \(event.group))" + let groupString = event.group.value.hex.stringValue(prefix: true) + return "tuneRequest(group: \(groupString))" // ---------------------- @@ -141,28 +161,28 @@ extension MIDI.Event: CustomStringConvertible, CustomDebugStringConvertible { // ---------------------- case .timingClock(let event): - - return "timingClock(group: \(event.group))" + let groupString = event.group.value.hex.stringValue(prefix: true) + return "timingClock(group: \(groupString))" case .start(let event): - - return "start(group: \(event.group))" + let groupString = event.group.value.hex.stringValue(prefix: true) + return "start(group: \(groupString))" case .continue(let event): - - return "continue(group: \(event.group))" + let groupString = event.group.value.hex.stringValue(prefix: true) + return "continue(group: \(groupString))" case .stop(let event): - - return "stop(group: \(event.group))" + let groupString = event.group.value.hex.stringValue(prefix: true) + return "stop(group: \(groupString))" case .activeSensing(let event): - - return "activeSensing(group: \(event.group))" + let groupString = event.group.value.hex.stringValue(prefix: true) + return "activeSensing(group: \(groupString))" case .systemReset(let event): - - return "systemReset(group: \(event.group))" + let groupString = event.group.value.hex.stringValue(prefix: true) + return "systemReset(group: \(groupString))" } diff --git a/Sources/MIDIKit/Events/Event/System Exclusive/SysEx Parser.swift b/Sources/MIDIKit/Events/Event/System Exclusive/SysEx Parser.swift index eda251f72b..7e2eb72a06 100644 --- a/Sources/MIDIKit/Events/Event/System Exclusive/SysEx Parser.swift +++ b/Sources/MIDIKit/Events/Event/System Exclusive/SysEx Parser.swift @@ -12,10 +12,10 @@ extension MIDI.Event { /// /// - Throws: `MIDI.Event.ParseError` if message is malformed. @inline(__always) - public static func sysEx7( - rawBytes: [MIDI.Byte], + public init( + sysEx7RawBytes rawBytes: [MIDI.Byte], group: MIDI.UInt4 = 0 - ) throws -> Self { + ) throws { var readPos = rawBytes.startIndex @@ -93,7 +93,7 @@ extension MIDI.Event { try readPosAdvance(by: 1) let data = try readData() - return .universalSysEx7( + self = .universalSysEx7( .init(universalType: universalType, deviceID: deviceID, subID1: subID1, @@ -101,6 +101,7 @@ extension MIDI.Event { data: data, group: group) ) + return case 0x00...0x7D: var readManufacturer: SysExManufacturer? @@ -139,11 +140,12 @@ extension MIDI.Event { data = try readData() } - return .sysEx7( + self = .sysEx7( .init(manufacturer: manufacturer, data: data, group: group) ) + return default: // malformed @@ -158,10 +160,10 @@ extension MIDI.Event { /// /// - Throws: `MIDI.Event.ParseError` if message is malformed. @inline(__always) - public static func sysEx8( - rawBytes: [MIDI.Byte], + public init( + sysEx8RawBytes rawBytes: [MIDI.Byte], group: MIDI.UInt4 = 0 - ) throws -> Self { + ) throws { var readPos = rawBytes.startIndex @@ -220,7 +222,7 @@ extension MIDI.Event { } }() - return .universalSysEx8( + self = .universalSysEx8( .init(universalType: universalType, deviceID: deviceID, subID1: subID1, @@ -229,6 +231,7 @@ extension MIDI.Event { streamID: streamID, group: group) ) + return case .manufacturer(let mfr): var data: [MIDI.Byte] = [] @@ -240,12 +243,13 @@ extension MIDI.Event { } } - return .sysEx8( + self = .sysEx8( .init(manufacturer: mfr, data: data, streamID: streamID, group: group) ) + return default: // malformed diff --git a/Sources/MIDIKit/Events/Event/System Exclusive/SysExID.swift b/Sources/MIDIKit/Events/Event/System Exclusive/SysExID.swift index 2932c71696..50140bfe1e 100644 --- a/Sources/MIDIKit/Events/Event/System Exclusive/SysExID.swift +++ b/Sources/MIDIKit/Events/Event/System Exclusive/SysExID.swift @@ -72,3 +72,35 @@ extension MIDI.Event.SysExID { } } + +extension MIDI.Event.SysExID { + + /// Returns the Manufacturer byte(s) formatted for MIDI 1.0 SysEx7, as one byte (7-bit) or three bytes (21-bit). + @inline(__always) + public func sysEx7RawBytes() -> [MIDI.Byte] { + + switch self { + case .manufacturer(let mfr): + return mfr.sysEx7RawBytes() + + case .universal(let uSysEx): + return [uSysEx.rawValue.uInt8Value] + } + + } + + /// Returns the Manufacturer byte(s) formatted for MIDI 2.0 SysEx8, as two bytes (16-bit). + @inline(__always) + public func sysEx8RawBytes() -> [MIDI.Byte] { + + switch self { + case .manufacturer(let mfr): + return mfr.sysEx8RawBytes() + + case .universal(let uSysEx): + return [0x00, uSysEx.rawValue.uInt8Value] + } + + } + +} diff --git a/Sources/MIDIKit/Events/Event/System Exclusive/SysExManufacturer.swift b/Sources/MIDIKit/Events/Event/System Exclusive/SysExManufacturer.swift index 92bd3b1d1c..c3a59dc5ef 100644 --- a/Sources/MIDIKit/Events/Event/System Exclusive/SysExManufacturer.swift +++ b/Sources/MIDIKit/Events/Event/System Exclusive/SysExManufacturer.swift @@ -153,9 +153,9 @@ extension MIDI.Event.SysExManufacturer { return (0x01...0x7D).contains(byte) case .threeByte(byte2: let byte2, byte3: let byte3): - return - (0x00...0x7F).contains(byte2) && - (0x00...0x7F).contains(byte3) + // both can't be 0x00, at least one has to be non-zero. + // all other scenarios are valid + return !(byte2 == 0x00 && byte3 == 0x00) } } diff --git a/Sources/MIDIKit/IO/API/APIVersion.swift b/Sources/MIDIKit/IO/API/APIVersion.swift index 7a18a3d0ee..5baa60cd61 100644 --- a/Sources/MIDIKit/IO/API/APIVersion.swift +++ b/Sources/MIDIKit/IO/API/APIVersion.swift @@ -67,9 +67,13 @@ extension MIDI.IO.APIVersion { switch self { case .legacyCoreMIDI: #if os(macOS) + // Apple has deprecated legacy API but not yet obsoleted. + // We'll guess that it may become obsoleted as of macOS 13.0 if #available(macOS 13, *) { return false } return true #elseif os(iOS) + // Apple has deprecated legacy API but not yet obsoleted. + // We'll guess that it may become obsoleted as of iOS 16.0 if #available(iOS 16, *) { return false } return true #elseif os(tvOS) || os(watchOS) diff --git a/Sources/MIDIKit/IO/Managed/Input.swift b/Sources/MIDIKit/IO/Managed/Input.swift index 3a84dd4493..8266bb747a 100644 --- a/Sources/MIDIKit/IO/Managed/Input.swift +++ b/Sources/MIDIKit/IO/Managed/Input.swift @@ -31,7 +31,7 @@ extension MIDI.IO { public private(set) var uniqueID: MIDI.IO.InputEndpoint.UniqueID? = nil /// The Core MIDI port reference. - internal var coreMIDIPortRef: MIDI.IO.CoreMIDIPortRef? = nil + public private(set) var coreMIDIInputPortRef: MIDI.IO.CoreMIDIPortRef? = nil internal var receiveHandler: MIDI.IO.ReceiveHandler @@ -149,7 +149,7 @@ extension MIDI.IO.Input { } - coreMIDIPortRef = newPortRef + coreMIDIInputPortRef = newPortRef // set meta data properties; ignore errors in case of failure _ = try? MIDI.IO.setModel(of: newPortRef, to: manager.model) @@ -172,9 +172,9 @@ extension MIDI.IO.Input { /// Errors thrown can be safely ignored and are typically only useful for debugging purposes. internal func dispose() throws { - guard let unwrappedPortRef = self.coreMIDIPortRef else { return } + guard let unwrappedPortRef = self.coreMIDIInputPortRef else { return } - defer { self.coreMIDIPortRef = nil } + defer { self.coreMIDIInputPortRef = nil } try MIDIEndpointDispose(unwrappedPortRef) .throwIfOSStatusErr() diff --git a/Sources/MIDIKit/IO/Managed/InputConnection.swift b/Sources/MIDIKit/IO/Managed/InputConnection.swift index 8bd5ee7919..dbc3d80768 100644 --- a/Sources/MIDIKit/IO/Managed/InputConnection.swift +++ b/Sources/MIDIKit/IO/Managed/InputConnection.swift @@ -26,8 +26,12 @@ extension MIDI.IO { // class-specific public private(set) var outputsCriteria: [MIDI.IO.EndpointIDCriteria] - internal var coreMIDIOutputEndpointRefs: [MIDI.IO.CoreMIDIEndpointRef?] = [] - internal var coreMIDIInputPortRef: MIDI.IO.CoreMIDIPortRef? = nil + + /// The Core MIDI input port reference. + public private(set) var coreMIDIInputPortRef: MIDI.IO.CoreMIDIPortRef? = nil + + /// The Core MIDI output endpoint(s) reference(s). + public private(set) var coreMIDIOutputEndpointRefs: [MIDI.IO.CoreMIDIEndpointRef?] = [] internal var receiveHandler: MIDI.IO.ReceiveHandler diff --git a/Sources/MIDIKit/IO/Managed/Output.swift b/Sources/MIDIKit/IO/Managed/Output.swift index 4af1f0fc2b..4447ea8975 100644 --- a/Sources/MIDIKit/IO/Managed/Output.swift +++ b/Sources/MIDIKit/IO/Managed/Output.swift @@ -22,8 +22,10 @@ extension MIDI.IO { public private(set) var api: APIVersion public var midiProtocol: MIDI.IO.ProtocolVersion { api.midiProtocol } - // _MIDIIOSendsMIDIMessagesProtocol - internal var coreMIDIOutputPortRef: MIDI.IO.CoreMIDIPortRef? = nil + // MIDIIOSendsMIDIMessagesProtocol + + /// The Core MIDI output port reference. + public private(set) var coreMIDIOutputPortRef: MIDI.IO.CoreMIDIPortRef? = nil // class-specific diff --git a/Sources/MIDIKit/IO/Managed/OutputConnection.swift b/Sources/MIDIKit/IO/Managed/OutputConnection.swift index 957f918b68..0dbf462ec3 100644 --- a/Sources/MIDIKit/IO/Managed/OutputConnection.swift +++ b/Sources/MIDIKit/IO/Managed/OutputConnection.swift @@ -23,13 +23,17 @@ extension MIDI.IO { public private(set) var api: MIDI.IO.APIVersion public var midiProtocol: MIDI.IO.ProtocolVersion { api.midiProtocol } - // _MIDIIOSendsMIDIMessagesProtocol - internal var coreMIDIOutputPortRef: MIDI.IO.CoreMIDIPortRef? = nil + // MIDIIOSendsMIDIMessagesProtocol + + /// The Core MIDI output port reference. + public private(set) var coreMIDIOutputPortRef: MIDI.IO.CoreMIDIPortRef? = nil // class-specific public private(set) var inputsCriteria: [MIDI.IO.EndpointIDCriteria] - internal var coreMIDIInputEndpointRefs: [MIDI.IO.CoreMIDIEndpointRef?] = [] + + /// The Core MIDI input endpoint(s) reference(s). + public private(set) var coreMIDIInputEndpointRefs: [MIDI.IO.CoreMIDIEndpointRef?] = [] // init diff --git a/Sources/MIDIKit/IO/Managed/Protocols/MIDIIOSendsMIDIMessagesProtocol.swift b/Sources/MIDIKit/IO/Managed/Protocols/MIDIIOSendsMIDIMessagesProtocol.swift index 844bb59212..aab5b8acc6 100644 --- a/Sources/MIDIKit/IO/Managed/Protocols/MIDIIOSendsMIDIMessagesProtocol.swift +++ b/Sources/MIDIKit/IO/Managed/Protocols/MIDIIOSendsMIDIMessagesProtocol.swift @@ -9,6 +9,9 @@ public protocol MIDIIOSendsMIDIMessagesProtocol: MIDIIOManagedProtocol { + /// The Core MIDI output port ref. + /* public private(set) */ var coreMIDIOutputPortRef: MIDI.IO.CoreMIDIPortRef? { get } + /// MIDI Protocol version used for this endpoint. /* public private(set) */ var midiProtocol: MIDI.IO.ProtocolVersion { get } @@ -24,10 +27,6 @@ public protocol MIDIIOSendsMIDIMessagesProtocol: MIDIIOManagedProtocol { internal protocol _MIDIIOSendsMIDIMessagesProtocol: MIDIIOSendsMIDIMessagesProtocol { - /// Internal: - /// Core MIDI Port Ref(s) - var coreMIDIOutputPortRef: MIDI.IO.CoreMIDIPortRef? { get } - /// Internal: /// Send a MIDI Message, automatically assembling it into a `MIDIPacketList`. /// diff --git a/Sources/MIDIKit/IO/Managed/ThruConnection/ThruConnection.swift b/Sources/MIDIKit/IO/Managed/ThruConnection/ThruConnection.swift index 32c42ca74a..fdc9371fd7 100644 --- a/Sources/MIDIKit/IO/Managed/ThruConnection/ThruConnection.swift +++ b/Sources/MIDIKit/IO/Managed/ThruConnection/ThruConnection.swift @@ -32,7 +32,7 @@ extension MIDI.IO { // class-specific - internal var coreMIDIThruConnectionRef: MIDI.IO.CoreMIDIThruConnectionRef? = nil + public private(set) var coreMIDIThruConnectionRef: MIDI.IO.CoreMIDIThruConnectionRef? = nil public private(set) var outputs: [OutputEndpoint] public private(set) var inputs: [InputEndpoint] public private(set) var lifecycle: Lifecycle diff --git a/Sources/MIDIKit/IO/Manager/Manager.swift b/Sources/MIDIKit/IO/Manager/Manager.swift index 4a479ae12b..0b308cd1c2 100644 --- a/Sources/MIDIKit/IO/Manager/Manager.swift +++ b/Sources/MIDIKit/IO/Manager/Manager.swift @@ -103,10 +103,7 @@ extension MIDI.IO { ) { // API version - // Currently, legacy API is recommended as it is more stable. (New API is experimental due to bugs in Core MIDI itself, until workarounds or resolutions can be found.) - // So we will default to legacy here if possible. - preferredAPI = APIVersion.legacyCoreMIDI.isValidOnCurrentPlatform - ? .legacyCoreMIDI : .bestForPlatform() + preferredAPI = .bestForPlatform() // queue client name var clientNameForQueue = clientName.onlyAlphanumerics diff --git a/Sources/MIDIKit/Parser/MIDI1Parser.swift b/Sources/MIDIKit/Parser/MIDI1Parser.swift index 4b1ed1fb97..7408c94d25 100644 --- a/Sources/MIDIKit/Parser/MIDI1Parser.swift +++ b/Sources/MIDIKit/Parser/MIDI1Parser.swift @@ -503,7 +503,7 @@ extension MIDI { case 0xF: // system message switch statusByte.nibbles.low { case 0x0: // System Common - SysEx Start - guard let parsedSysEx = try? MIDI.Event.sysEx7(rawBytes: bytes) + guard let parsedSysEx = try? MIDI.Event(sysEx7RawBytes: bytes) else { return events } events.append(parsedSysEx) diff --git a/Sources/MIDIKit/Parser/MIDI2Parser.swift b/Sources/MIDIKit/Parser/MIDI2Parser.swift index b6482c1bef..53eef7e1bd 100644 --- a/Sources/MIDIKit/Parser/MIDI2Parser.swift +++ b/Sources/MIDIKit/Parser/MIDI2Parser.swift @@ -559,8 +559,8 @@ extension MIDI { switch sysExStatusField { case .complete: - guard let parsedSysEx = try? MIDI.Event.sysEx7( - rawBytes: [0xF0] + payloadBytes + [0xF7], + guard let parsedSysEx = try? MIDI.Event( + sysEx7RawBytes: [0xF0] + payloadBytes + [0xF7], group: group ) else { return nil } @@ -589,8 +589,8 @@ extension MIDI { // reset buffer sysEx7MultiPartUMPBuffer = [] - guard let parsedSysEx = try? MIDI.Event.sysEx7( - rawBytes: [0xF0] + fullData + [0xF7], + guard let parsedSysEx = try? MIDI.Event( + sysEx7RawBytes: [0xF0] + fullData + [0xF7], group: group ) else { return nil } @@ -635,8 +635,8 @@ extension MIDI { bytes.startIndex.advanced(by: 1 + numberOfBytes) ] - guard let parsedSysEx = try? MIDI.Event.sysEx8( - rawBytes: Array(payloadBytes), + guard let parsedSysEx = try? MIDI.Event( + sysEx8RawBytes: Array(payloadBytes), group: group ) else { return nil } @@ -688,8 +688,8 @@ extension MIDI { let fullData = buffer + Array(payloadBytes) - guard let parsedSysEx = try? MIDI.Event.sysEx8( - rawBytes: fullData, + guard let parsedSysEx = try? MIDI.Event( + sysEx8RawBytes: fullData, group: group ) else { return nil } diff --git a/Tests/MIDIKitTests/Events/Event/SysEx/SysEx7 Tests.swift b/Tests/MIDIKitTests/Events/Event/SysEx/SysEx7 Tests.swift index 83d00609fe..783f98807b 100644 --- a/Tests/MIDIKitTests/Events/Event/SysEx/SysEx7 Tests.swift +++ b/Tests/MIDIKitTests/Events/Event/SysEx/SysEx7 Tests.swift @@ -7,115 +7,119 @@ import XCTest @testable import MIDIKit +import SwiftRadix class SysEx7Tests: XCTestCase { - func testInit_RawBytes_Typical() throws { + func testSysEx7RawBytes_Typical() throws { let sourceRawBytes: [MIDI.Byte] = [0xF0, 0x41, 0x01, 0x34, 0xF7] - let event = try MIDI.Event.sysEx7(rawBytes: sourceRawBytes) + let event = try MIDI.Event(sysEx7RawBytes: sourceRawBytes) guard case .sysEx7(let innerEvent) = event else { XCTFail() ; return } - XCTAssertEqual(innerEvent.manufacturer.sysEx7RawBytes(), [0x41]) + XCTAssertEqual(innerEvent.manufacturer, .oneByte(0x41)) XCTAssertEqual(innerEvent.data, [0x01, 0x34]) XCTAssertEqual(innerEvent.group, 0) XCTAssertEqual(event.midi1RawBytes, sourceRawBytes) - #warning("> TODO: also test umpRawBytes here") + XCTAssertEqual(event.umpRawWords(protocol: ._2_0), + [[0x30034101, 0x34000000]]) } - func testInit_RawBytes_EmptyMessageBytes_WithMfr_WithEndByte() throws { + func testSysEx7RawBytes_EmptyMessageBytes_WithMfr_WithEndByte() throws { let sourceRawBytes: [MIDI.Byte] = [0xF0, 0x41, 0xF7] - let event = try MIDI.Event.sysEx7(rawBytes: sourceRawBytes) + let event = try MIDI.Event(sysEx7RawBytes: sourceRawBytes) guard case .sysEx7(let innerEvent) = event else { XCTFail() ; return } - XCTAssertEqual(innerEvent.manufacturer.sysEx7RawBytes(), [0x41]) + XCTAssertEqual(innerEvent.manufacturer, .oneByte(0x41)) XCTAssertEqual(innerEvent.data, []) XCTAssertEqual(innerEvent.group, 0) XCTAssertEqual(event.midi1RawBytes, sourceRawBytes) - #warning("> TODO: also test umpRawBytes here") + XCTAssertEqual(event.umpRawWords(protocol: ._2_0), + [[0x30014100, 0x00000000]]) } - func testInit_RawBytes_EmptyMessageBytes_WithMfr() throws { + func testSysEx7RawBytes_EmptyMessageBytes_WithMfr() throws { let sourceRawBytes: [MIDI.Byte] = [0xF0, 0x41] - let event = try MIDI.Event.sysEx7(rawBytes: sourceRawBytes) + let event = try MIDI.Event(sysEx7RawBytes: sourceRawBytes) guard case .sysEx7(let innerEvent) = event else { XCTFail() ; return } - XCTAssertEqual(innerEvent.manufacturer.sysEx7RawBytes(), [0x41]) + XCTAssertEqual(innerEvent.manufacturer, .oneByte(0x41)) XCTAssertEqual(innerEvent.data, []) XCTAssertEqual(innerEvent.group, 0) XCTAssertEqual(event.midi1RawBytes, [0xF0, 0x41, 0xF7]) - #warning("> TODO: also test umpRawBytes here") + XCTAssertEqual(event.umpRawWords(protocol: ._2_0), + [[0x30014100, 0x00000000]]) } - func testInit_RawBytes_EmptyMessageBytes_WithEndByte() { + func testSysEx7RawBytes_EmptyMessageBytes_WithEndByte() { let sourceRawBytes: [MIDI.Byte] = [0xF0, 0xF7] XCTAssertThrowsError( - try MIDI.Event.sysEx7(rawBytes: sourceRawBytes) + try MIDI.Event(sysEx7RawBytes: sourceRawBytes) ) } - func testInit_RawBytes_MaxSize() { + func testSysEx7RawBytes_MaxSize() { // valid - maximum byte length (256 bytes) XCTAssertNoThrow( - try MIDI.Event.sysEx7( - rawBytes: [0xF0, 0x41] + try MIDI.Event( + sysEx7RawBytes: [0xF0, 0x41] + [MIDI.Byte](repeating: 0x20, count: 256-3) + [0xF7]) ) // valid - length is larger than default 256 bytes (257 bytes) XCTAssertNoThrow( - try MIDI.Event.sysEx7( - rawBytes: [0xF0, 0x41] + try MIDI.Event( + sysEx7RawBytes: [0xF0, 0x41] + [MIDI.Byte](repeating: 0x20, count: 256-2) + [0xF7]) ) } - func testInit_RawBytes_Malformed() { + func testSysEx7RawBytes_Malformed() { // empty raw bytes - invalid XCTAssertThrowsError( - try MIDI.Event.sysEx7(rawBytes: []) + try MIDI.Event(sysEx7RawBytes: []) ) // start byte only - invalid XCTAssertThrowsError( - try MIDI.Event.sysEx7(rawBytes: [0xF0]) + try MIDI.Event(sysEx7RawBytes: [0xF0]) ) // end byte only - invalid XCTAssertThrowsError( - try MIDI.Event.sysEx7(rawBytes: [0xF7]) + try MIDI.Event(sysEx7RawBytes: [0xF7]) ) // start and end bytes only - invalid XCTAssertThrowsError( - try MIDI.Event.sysEx7(rawBytes: [0xF0, 0xF7]) + try MIDI.Event(sysEx7RawBytes: [0xF0, 0xF7]) ) // correct start byte, valid length, but incorrect end byte XCTAssertThrowsError( - try MIDI.Event.sysEx7(rawBytes: [0xF0, 0x41, 0x01, 0x34, 0xF6]) + try MIDI.Event(sysEx7RawBytes: [0xF0, 0x41, 0x01, 0x34, 0xF6]) ) } @@ -124,10 +128,10 @@ class SysEx7Tests: XCTestCase { // ensure instances equate correctly - let event1A = try MIDI.Event.sysEx7(rawBytes: [0xF0, 0x41, 0x01, 0x34, 0xF7]) - let event1B = try MIDI.Event.sysEx7(rawBytes: [0xF0, 0x41, 0x01, 0x34, 0xF7]) + let event1A = try MIDI.Event(sysEx7RawBytes: [0xF0, 0x41, 0x01, 0x34, 0xF7]) + let event1B = try MIDI.Event(sysEx7RawBytes: [0xF0, 0x41, 0x01, 0x34, 0xF7]) - let event2 = try MIDI.Event.sysEx7(rawBytes: [0xF0, 0x41, 0x01, 0x64, 0xF7]) + let event2 = try MIDI.Event(sysEx7RawBytes: [0xF0, 0x41, 0x01, 0x64, 0xF7]) XCTAssert(event1A == event1B) @@ -139,10 +143,10 @@ class SysEx7Tests: XCTestCase { // ensure instances hash correctly - let event1A = try MIDI.Event.sysEx7(rawBytes: [0xF0, 0x41, 0x01, 0x34, 0xF7]) - let event1B = try MIDI.Event.sysEx7(rawBytes: [0xF0, 0x41, 0x01, 0x34, 0xF7]) + let event1A = try MIDI.Event(sysEx7RawBytes: [0xF0, 0x41, 0x01, 0x34, 0xF7]) + let event1B = try MIDI.Event(sysEx7RawBytes: [0xF0, 0x41, 0x01, 0x34, 0xF7]) - let event2 = try MIDI.Event.sysEx7(rawBytes: [0xF0, 0x41, 0x01, 0x64, 0xF7]) + let event2 = try MIDI.Event(sysEx7RawBytes: [0xF0, 0x41, 0x01, 0x64, 0xF7]) let set1: Set = [event1A, event1B] diff --git a/Tests/MIDIKitTests/Events/Event/SysEx/SysEx8 Tests.swift b/Tests/MIDIKitTests/Events/Event/SysEx/SysEx8 Tests.swift index 9cb3e9efa9..1781cbe74d 100644 --- a/Tests/MIDIKitTests/Events/Event/SysEx/SysEx8 Tests.swift +++ b/Tests/MIDIKitTests/Events/Event/SysEx/SysEx8 Tests.swift @@ -10,31 +10,139 @@ import XCTest class SysEx8Tests: XCTestCase { - func testInit_RawBytes_SingleUMP() throws { + func testSysEx8_SingleUMP() throws { let sourceRawBytes: [MIDI.Byte] = [0x00, // stream ID - 0x00, 0x41, // sysEx ID + 0x00, 0x7D, // sysEx ID 0x01, 0x34, 0xE6] // data bytes - let event = try MIDI.Event.sysEx8(rawBytes: sourceRawBytes) + let event = try MIDI.Event(sysEx8RawBytes: sourceRawBytes) guard case .sysEx8(let innerEvent) = event else { XCTFail() ; return } - XCTAssertEqual(innerEvent.manufacturer.sysEx8RawBytes(), [0x00, 0x41]) + XCTAssertEqual(innerEvent.manufacturer, .oneByte(0x7D)) XCTAssertEqual(innerEvent.data, [0x01, 0x34, 0xE6]) XCTAssertEqual(innerEvent.group, 0) XCTAssertEqual(event.umpRawWords(protocol: ._2_0), [ [0x50060000, - 0x410134E6, + 0x7D0134E6, 0x00000000, 0x00000000] ]) } - func testInit_RawBytes_MultiPart_UMP() throws { + func testSysEx8_2Part_UMP() throws { + + let event = MIDI.Event.sysEx8(manufacturer: .threeByte(byte2: 0x00, byte3: 0x66), + data: [0x01, 0x02, 0x03, 0x01, + 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, + 0x0A, 0x0B, 0x0C, 0xE6], + group: 0) + + guard case .sysEx8(let innerEvent) = event + else { XCTFail() ; return } + + XCTAssertEqual(innerEvent.manufacturer, .threeByte(byte2: 0x00, byte3: 0x66)) + XCTAssertEqual(innerEvent.data, [0x01, 0x02, 0x03, 0x01, + 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, + 0x0A, 0x0B, 0x0C, 0xE6]) + XCTAssertEqual(innerEvent.group, 0) + + XCTAssertEqual(event.umpRawWords(protocol: ._2_0), + [ + [0x501E0080, + 0x66010203, + 0x01020304, + 0x05060708], + [0x50360009, + 0x0A0B0CE6, + 0x00000000, + 0x00000000] + ]) + + } + + func testSysEx8_3Part_UMP() throws { + + let event = MIDI.Event.sysEx8(manufacturer: .threeByte(byte2: 0x21, byte3: 0x09), + data: [0x01, 0x02, 0x03, 0x01, + 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, + 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, + 0xE6], + group: 0) + + guard case .sysEx8(let innerEvent) = event + else { XCTFail() ; return } + + XCTAssertEqual(innerEvent.manufacturer, .threeByte(byte2: 0x21, byte3: 0x09)) + XCTAssertEqual(innerEvent.data, [0x01, 0x02, 0x03, 0x01, + 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, + 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, + 0xE6]) + XCTAssertEqual(innerEvent.group, 0) + + XCTAssertEqual(event.umpRawWords(protocol: ._2_0), + [ + [0x501E00A1, + 0x09010203, + 0x01020304, + 0x05060708], + [0x502E0009, + 0x0A0B0C0D, + 0x0E0F1011, + 0x12131415], + [0x50360016, + 0x171819E6, + 0x00000000, + 0x00000000] + ]) + + } + + func testUniversalSysEx8_SingleUMP() throws { + + let event = MIDI.Event.universalSysEx8(universalType: .realTime, + deviceID: 0x01, + subID1: 0x02, + subID2: 0x03, + data: [0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0xE6]) + + guard case .universalSysEx8(let innerEvent) = event + else { XCTFail() ; return } + + XCTAssertEqual(innerEvent.universalType, .realTime) + XCTAssertEqual(innerEvent.deviceID, 0x01) + XCTAssertEqual(innerEvent.subID1, 0x02) + XCTAssertEqual(innerEvent.subID2, 0x03) + XCTAssertEqual(innerEvent.data, [0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0xE6]) + XCTAssertEqual(innerEvent.group, 0) + + XCTAssertEqual(event.umpRawWords(protocol: ._2_0), + [ + [0x500E0000, + 0x7F010203, + 0x01020304, + 0x050607E6] + ]) + + } + + func testUniversalSysEx8_2Part_UMP() throws { let event = MIDI.Event.universalSysEx8(universalType: .realTime, deviceID: 0x01, @@ -68,25 +176,69 @@ class SysEx8Tests: XCTestCase { 0x00000000] ]) + } + + func testUniversalSysEx8_3Part_UMP() throws { + + let event = MIDI.Event.universalSysEx8(universalType: .nonRealTime, + deviceID: 0x01, + subID1: 0x02, + subID2: 0x03, + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0xE6]) + + guard case .universalSysEx8(let innerEvent) = event + else { XCTFail() ; return } + + XCTAssertEqual(innerEvent.universalType, .nonRealTime) + XCTAssertEqual(innerEvent.deviceID, 0x01) + XCTAssertEqual(innerEvent.subID1, 0x02) + XCTAssertEqual(innerEvent.subID2, 0x03) + XCTAssertEqual(innerEvent.data, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0xE6]) + XCTAssertEqual(innerEvent.group, 0) + + XCTAssertEqual(event.umpRawWords(protocol: ._2_0), + [ + [0x501E0000, + 0x7E010203, + 0x01020304, + 0x05060708], + [0x502E0009, + 0x0A0B0C0D, + 0x0E0F1011, + 0x12131415], + [0x50360016, + 0x171819E6, + 0x00000000, + 0x00000000] + ]) + } - func testInit_RawBytes_Malformed() { + func testSysEx8RawBytes_Malformed() { // empty raw bytes - invalid XCTAssertThrowsError( - try MIDI.Event.sysEx8(rawBytes: []) + try MIDI.Event(sysEx8RawBytes: []) ) // start byte only - invalid when in a complete SysEx8 UMP message XCTAssertThrowsError( - try MIDI.Event.sysEx8(rawBytes: [0x00]) + try MIDI.Event(sysEx8RawBytes: [0x00]) ) - // invalid sysEx ID - XCTAssertThrowsError( - try MIDI.Event.sysEx8(rawBytes: [0x00, // stream ID - 0x00, 0x80, // sysEx ID -- invalid - 0x01, 0x34, 0xE6]) // data bytes + // invalid sysEx ID + XCTAssertThrowsError( + try MIDI.Event(sysEx8RawBytes: [0x00, // stream ID + 0x00, 0x80, // sysEx ID -- invalid + 0x01, 0x34, 0xE6]) // data bytes ) } @@ -95,18 +247,18 @@ class SysEx8Tests: XCTestCase { // ensure instances equate correctly - let event1A = try MIDI.Event.sysEx8(rawBytes: [0x00, // stream ID - 0x00, 0x41, // sysEx ID - 0x01, 0x34, 0xE6]) // data bytes) - let event1B = try MIDI.Event.sysEx8(rawBytes: [0x00, // stream ID - 0x00, 0x41, // sysEx ID - 0x01, 0x34, 0xE6]) // data bytes) - - let event2 = try MIDI.Event.sysEx8(rawBytes: [0x00, // stream ID - 0x00, 0x42, // sysEx ID - 0x01, 0x34, 0xE5]) // data bytes) - - XCTAssert(event1A == event1B) + let event1A = try MIDI.Event(sysEx8RawBytes: [0x00, // stream ID + 0x00, 0x41, // sysEx ID + 0x01, 0x34, 0xE6]) // data bytes) + let event1B = try MIDI.Event(sysEx8RawBytes: [0x00, // stream ID + 0x00, 0x41, // sysEx ID + 0x01, 0x34, 0xE6]) // data bytes) + + let event2 = try MIDI.Event(sysEx8RawBytes: [0x00, // stream ID + 0x00, 0x42, // sysEx ID + 0x01, 0x34, 0xE5]) // data bytes) + + XCTAssert(event1A == event1B) XCTAssert(event1A != event2) @@ -115,17 +267,17 @@ class SysEx8Tests: XCTestCase { func testHashable() throws { // ensure instances hash correctly - - let event1A = try MIDI.Event.sysEx8(rawBytes: [0x00, // stream ID - 0x00, 0x41, // sysEx ID - 0x01, 0x34, 0xE6]) // data bytes) - let event1B = try MIDI.Event.sysEx8(rawBytes: [0x00, // stream ID - 0x00, 0x41, // sysEx ID - 0x01, 0x34, 0xE6]) // data bytes) - - let event2 = try MIDI.Event.sysEx8(rawBytes: [0x00, // stream ID - 0x00, 0x42, // sysEx ID - 0x01, 0x34, 0xE5]) // data bytes) + + let event1A = try MIDI.Event(sysEx8RawBytes: [0x00, // stream ID + 0x00, 0x41, // sysEx ID + 0x01, 0x34, 0xE6]) // data bytes) + let event1B = try MIDI.Event(sysEx8RawBytes: [0x00, // stream ID + 0x00, 0x41, // sysEx ID + 0x01, 0x34, 0xE6]) // data bytes) + + let event2 = try MIDI.Event(sysEx8RawBytes: [0x00, // stream ID + 0x00, 0x42, // sysEx ID + 0x01, 0x34, 0xE5]) // data bytes) let set1: Set = [event1A, event1B] diff --git a/Tests/MIDIKitTests/Events/Event/SysEx/SysExID Tests.swift b/Tests/MIDIKitTests/Events/Event/SysEx/SysExID Tests.swift index ec807ab507..6fef695e7d 100644 --- a/Tests/MIDIKitTests/Events/Event/SysEx/SysExID Tests.swift +++ b/Tests/MIDIKitTests/Events/Event/SysEx/SysExID Tests.swift @@ -146,6 +146,72 @@ class SysExIDTests: XCTestCase { } + func testManufacturer_sysEx7RawBytes() { + + XCTAssertEqual( + MIDI.Event.SysExID.manufacturer(.oneByte(0x01)).sysEx7RawBytes(), + [0x01] + ) + + XCTAssertEqual( + MIDI.Event.SysExID.manufacturer(.oneByte(0x7D)).sysEx7RawBytes(), + [0x7D] + ) + + XCTAssertEqual( + MIDI.Event.SysExID.manufacturer(.threeByte(byte2: 0x7F, byte3: 0x7F)).sysEx7RawBytes(), + [0x00, 0x7F, 0x7F] + ) + + } + + func testManufacturer_sysEx8RawBytes() { + + XCTAssertEqual( + MIDI.Event.SysExID.manufacturer(.oneByte(0x01)).sysEx8RawBytes(), + [0x00, 0x01] + ) + + XCTAssertEqual( + MIDI.Event.SysExID.manufacturer(.oneByte(0x7D)).sysEx8RawBytes(), + [0x00, 0x7D] + ) + + XCTAssertEqual( + MIDI.Event.SysExID.manufacturer(.threeByte(byte2: 0x7F, byte3: 0x7F)).sysEx8RawBytes(), + [0xFF, 0x7F] + ) + + } + + func testUniversal_sysEx7RawBytes() { + + XCTAssertEqual( + MIDI.Event.SysExID.universal(.nonRealTime).sysEx7RawBytes(), + [0x7E] + ) + + XCTAssertEqual( + MIDI.Event.SysExID.universal(.realTime).sysEx7RawBytes(), + [0x7F] + ) + + } + + func testUniversal_sysEx8RawBytes() { + + XCTAssertEqual( + MIDI.Event.SysExID.universal(.nonRealTime).sysEx8RawBytes(), + [0x00, 0x7E] + ) + + XCTAssertEqual( + MIDI.Event.SysExID.universal(.realTime).sysEx8RawBytes(), + [0x00, 0x7F] + ) + + } + } #endif diff --git a/Tests/MIDIKitTests/Events/Event/SysEx/SysExManufacturer Tests.swift b/Tests/MIDIKitTests/Events/Event/SysEx/SysExManufacturer Tests.swift index 6ab1bda695..58640a555f 100644 --- a/Tests/MIDIKitTests/Events/Event/SysEx/SysExManufacturer Tests.swift +++ b/Tests/MIDIKitTests/Events/Event/SysEx/SysExManufacturer Tests.swift @@ -44,9 +44,15 @@ class SysExManufacturerTests: XCTestCase { // valid conditions // min/max valid - XCTAssertTrue( + XCTAssertFalse( MIDI.Event.SysExManufacturer.threeByte(byte2: 0x00, byte3: 0x00).isValid ) + XCTAssertTrue( + MIDI.Event.SysExManufacturer.threeByte(byte2: 0x01, byte3: 0x00).isValid + ) + XCTAssertTrue( + MIDI.Event.SysExManufacturer.threeByte(byte2: 0x00, byte3: 0x01).isValid + ) XCTAssertTrue( MIDI.Event.SysExManufacturer.threeByte(byte2: 0x7F, byte3: 0x7F).isValid ) diff --git a/Tests/MIDIKitTests/IO/Manager/Manager Public Tests.swift b/Tests/MIDIKitTests/IO/Manager/Manager Public Tests.swift index 0d98392910..51ce9ecc03 100644 --- a/Tests/MIDIKitTests/IO/Manager/Manager Public Tests.swift +++ b/Tests/MIDIKitTests/IO/Manager/Manager Public Tests.swift @@ -43,8 +43,9 @@ final class Manager_Public_Tests: XCTestCase { let output = manager.managedOutputs.first?.value _ = output?.api - _ = output?.endpointName _ = output?.midiProtocol + _ = output?.coreMIDIOutputPortRef + _ = output?.endpointName _ = output?.uniqueID _ = output?.description @@ -59,7 +60,9 @@ final class Manager_Public_Tests: XCTestCase { _ = output?.api _ = output?.midiProtocol + _ = output?.coreMIDIOutputPortRef _ = output?.inputsCriteria + _ = output?.coreMIDIInputEndpointRefs _ = output?.description @@ -72,8 +75,9 @@ final class Manager_Public_Tests: XCTestCase { let input = manager.managedInputs.first?.value _ = input?.api - _ = input?.endpointName _ = input?.midiProtocol + _ = input?.coreMIDIInputPortRef + _ = input?.endpointName _ = input?.uniqueID _ = input?.description @@ -88,7 +92,9 @@ final class Manager_Public_Tests: XCTestCase { _ = input?.api _ = input?.midiProtocol + _ = input?.coreMIDIInputPortRef _ = input?.outputsCriteria + _ = input?.coreMIDIOutputEndpointRefs _ = input?.description @@ -101,6 +107,7 @@ final class Manager_Public_Tests: XCTestCase { let thru = manager.managedThruConnections.first?.value _ = thru?.api + _ = thru?.coreMIDIThruConnectionRef _ = thru?.inputs _ = thru?.outputs _ = thru?.lifecycle