broken changes

This commit is contained in:
Mark Gregory
2026-11-05 16:43:08 +00:00
parent 1b5e48dfa1
commit 13a6a9e1c8
6 changed files with 173 additions and 26 deletions
+29 -3
View File
@@ -20,6 +20,27 @@ struct DJXMLBinding: Codable, Equatable {
}
}
struct DJXMLOutput: Codable {
var key: String
var action: String
var status: UInt8
var number: UInt8
var on: UInt8
var off: UInt8
var flipped: Bool
func packet(active: Bool) -> [UInt8] { [status,number,(active != flipped) ? on : off] }
static func parse(_ output: [String:Any],binding: DJXMLBinding) -> Self? {
let supported = ["A Play","B Play","A Cue button","B Cue button","A Sync","B Sync","A Headphones","B Headphones","A Loop","B Loop"] + ["A","B"].flatMap { d in (1...4).map{d+" Hot cue \($0)"} }
guard supported.contains(binding.action),binding.status & 0xF0 == 0x90 else { return nil }
let channel = output["midiChannel"] as? Int ?? Int(binding.status & 15)
let number = output["midiData"] as? Int ?? Int(binding.number)
let type = output["midiMessageType"] as? Int ?? 1
guard (0...15).contains(channel),(0...127).contains(number),[0,1,3].contains(type) else { return nil }
func byte(_ key: String,_ fallback: Int) -> UInt8 { UInt8(min(127,max(0,(output[key] as? NSNumber)?.intValue ?? fallback))) }
return .init(key:binding.key,action:binding.action,status:UInt8((type == 0 ? 0x80 : type == 1 ? 0x90 : 0xB0)+channel),number:UInt8(number),on:byte("midiMaxValue",127),off:byte("midiDisabledValue",Int(byte("midiMinValue",0))),flipped:output["flipped"] as? Bool ?? false)
}
}
struct DJXMLProfile: Codable, Identifiable {
var id: String
var name: String
@@ -29,6 +50,7 @@ struct DJXMLProfile: Codable, Identifiable {
var total: Int
var aliases: [String]?
var adapter: String?
var outputs: [DJXMLOutput]?
var report: String {
"\(name)\n\(bindings.count) supported bindings of \(total) control entries.\n\n" +
(adapter == "Mixtrack Go" ? "Uses the dedicated Mixtrack Go adapter for 14-bit tempo, relative jog, pad modes and transport/mode feedback.\n\n" : "") +
@@ -40,7 +62,7 @@ struct DJXMLProfile: Codable, Identifiable {
(root["schemeVersion"] as? Int == 1 || root["schemeVersion"] as? String == "1") else { throw failure("Expected a djay mapping XML property list with schemeVersion 1.") }
let controls = root["controls"] as? [[String:Any]] ?? []
guard controls.count <= 20000 else { throw failure("Mapping contains too many controls.") }
var bindings = [DJXMLBinding](),issues = Set<String>(),blocked = Set<String>()
var bindings = [DJXMLBinding](),outputs = [DJXMLOutput](),issues = Set<String>(),blocked = Set<String>()
func address(_ c: [String:Any]) -> (UInt8,UInt8)? {
guard let channel = c["midiChannel"] as? Int,(0...15).contains(channel),let number = c["midiData"] as? Int,(0...127).contains(number),let type = c["midiMessageType"] as? Int,[1,3,6].contains(type) else { return nil }
return (UInt8((type == 1 ? 0x90 : type == 3 ? 0xB0 : 0xE0)+channel),UInt8(number))
@@ -63,13 +85,17 @@ struct DJXMLProfile: Codable, Identifiable {
blocked.insert(key);issues.insert("\(path): relative control is unsupported for this action.");continue
}
bindings.append(.init(status:status,number:number,action:action,mode:mode,flipped:c["flipped"] as? Bool ?? false,pickup:c["pickupMode"] as? Bool ?? false))
if c["output"] != nil { issues.insert("\(path): XML LED/display output is not implemented; input is supported where listed.") }
if let output = c["output"] as? [String:Any] {
if let feedback = DJXMLOutput.parse(output,binding:bindings.last!) { outputs.append(feedback) }
else { issues.insert("\(path): this XML output definition is not supported.") }
}
}
let grouped = Dictionary(grouping:bindings,by:{$0.key})
for (key,group) in grouped where Set(group.map(\.action)).count > 1 || Set(group.map(\.mode)).count > 1 || Set(group.map(\.flipped)).count > 1 {
blocked.insert(key);issues.insert("MIDI \(key): conflicting assignments disabled.")
}
bindings = grouped.keys.sorted().compactMap { blocked.contains($0) ? nil : grouped[$0]?.first }
outputs.removeAll { blocked.contains($0.key) }
if !blocked.isEmpty { issues.insert("All bindings sharing an unsupported/conditional address are disabled to prevent accidental actions.") }
if root["customClassName"] != nil { issues.insert("Device-specific djay custom processor is unavailable; only independent standard MIDI bindings are enabled.") }
if root["outputs"] != nil { issues.insert("Device output/display definitions are not executed.") }
@@ -79,7 +105,7 @@ struct DJXMLProfile: Codable, Identifiable {
bindings = MixtrackGoProtocol.mappings(source:0).map { .init(status:$0.status,number:$0.number,action:$0.action,mode:$0.mode,flipped:false,pickup:false) }
issues.insert("Mixtrack Go uses KJMix Pro’s dedicated protocol adapter, including its pad-mode state. Stems, sampler and FX modes remain unavailable.")
}
return .init(id:root["uuid"] as? String ?? filename,name:filename,endpoint:aliases.first ?? filename,bindings:bindings,issues:issues.sorted(),total:controls.filter{$0["headingTitle"] == nil}.count,aliases:aliases,adapter:adapter)
return .init(id:root["uuid"] as? String ?? filename,name:filename,endpoint:aliases.first ?? filename,bindings:bindings,issues:issues.sorted(),total:controls.filter{$0["headingTitle"] == nil}.count,aliases:aliases,adapter:adapter,outputs:outputs)
}
static func failure(_ message: String) -> NSError { NSError(domain:"DJ XML Mapping",code:1,userInfo:[NSLocalizedDescriptionKey:message]) }
static func normalized(_ name: String) -> String {