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
+77 -1
View File
@@ -1767,7 +1767,7 @@ extension KJamsCloneTests {
XCTAssertEqual(MIDISourceCreate(client,"KJMix virtual test controller" as CFString,&source),noErr)
let suite = "KJMix.VirtualMIDI.\(UUID().uuidString)",defaults = try XCTUnwrap(UserDefaults(suiteName:suite))
defer { defaults.removePersistentDomain(forName:suite) }
let midi = DJMIDI(defaults:defaults);midi.refreshSources()
let midi = DJMIDI(defaults:defaults,catalog:[]);midi.refreshSources()
let input = try XCTUnwrap(midi.sources.first{$0.endpoint == source});midi.select(input.id)
XCTAssertEqual(midi.sourceID,input.id)
XCTAssertEqual(defaults.integer(forKey:"DJ.MIDISource.v1"),Int(input.id))
@@ -2075,6 +2075,82 @@ extension KJamsCloneTests {
let data = try PropertyListSerialization.data(fromPropertyList:["schemeVersion":1,"uuid":"xml-test","endpointName":endpoint,"controls":controls],format:.xml,options:0)
return try DJXMLProfile.parse(data,filename:endpoint)
}
func testStereoRoutingMapsAndRejectsPublicCueOverlap() throws {
XCTAssertEqual(try DJAudioRouting.channelMap(channels:4,first:2),[-1,-1,0,1])
XCTAssertEqual(try DJAudioRouting.channelMap(channels:4,first:0),[0,1,-1,-1])
XCTAssertThrowsError(try DJAudioRouting.channelMap(channels:2,first:2))
let device = DJAudioDevice(id:1,uid:"controller",name:"Controller",outputChannels:4)
var routing = DJAudioRouting();routing.main.uid = device.uid;routing.cue = .init(uid:device.uid,firstChannel:2)
XCTAssertNoThrow(try routing.validate(devices:[device],defaultID:1))
routing.cue.firstChannel = 0;XCTAssertThrowsError(try routing.validate(devices:[device],defaultID:1))
routing.cue.uid = "";routing.mode = "External";routing.deckA.uid = device.uid;routing.deckB = .init(uid:device.uid,firstChannel:2)
XCTAssertNoThrow(try routing.validate(devices:[device],defaultID:1))
routing.deckB.firstChannel = 0;XCTAssertThrowsError(try routing.validate(devices:[device],defaultID:1))
}
@MainActor func testAudioOutputsAndActualFourChannelRoutingWhenInterfaceAttached() async throws {
let outputs = DJAudioDevice.outputs()
XCTAssertFalse(outputs.isEmpty,"CoreAudio should expose available Mac and USB outputs")
guard let controller = outputs.first(where:{$0.name.localizedCaseInsensitiveContains("MixTrack Go") && $0.outputChannels >= 4}) else { throw XCTSkip("Four-channel Mixtrack Go is not attached") }
let player = Player(persistsMix:false)
try player.routeOutput(controller.id,firstChannel:2)
XCTAssertEqual(player.outputChannelMap(),[-1,-1,0,1])
try player.routeOutput(controller.id,firstChannel:0)
XCTAssertEqual(player.outputChannelMap(),[0,1,-1,-1])
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
defer { try? FileManager.default.removeItem(at:root) }
let model = AppModel(storageURL:root)
var routing = DJAudioRouting();routing.main.uid = controller.uid;routing.cue = .init(uid:controller.uid,firstChannel:2)
model.dj.applyRouting(routing);XCTAssertNil(model.dj.error);XCTAssertEqual(model.dj.routing,routing)
XCTAssertEqual(DJSystem(storageURL:model.dj.root).routing,routing)
let host = NSHostingView(rootView:StudioSettingsDialog(model:model,scope:"DJ",category:"Devices").preferredColorScheme(.dark))
host.frame = NSRect(x:0,y:0,width:850,height:700)
let window = NSWindow(contentRect:host.frame,styleMask:[.borderless],backing:.buffered,defer:false)
window.contentView = host;window.setFrameOrigin(NSPoint(x:-10000,y:-10000));window.orderFront(nil)
defer { window.orderOut(nil);window.contentView = nil }
try await Task.sleep(nanoseconds:150_000_000);host.layoutSubtreeIfNeeded()
if let rep = host.bitmapImageRepForCachingDisplay(in:host.bounds) { host.cacheDisplay(in:host.bounds,to:rep);try XCTUnwrap(rep.representation(using:.png,properties:[:])).write(to:URL(fileURLWithPath:"/private/tmp/kjmix-xml-mappings/audio-routing.png")) }
}
func testXMLStandardLEDOutputHonoursOverridesAndInversion() throws {
let profile = try xmlMapping([["keyPath":"turntable1.playPause","midiMessageType":1,"midiChannel":0,"midiData":4,"output":["midiChannel":2,"midiData":9,"midiMessageType":3,"midiMinValue":1,"midiMaxValue":100,"flipped":true]]])
let output = try XCTUnwrap(profile.outputs?.first)
XCTAssertEqual(output.packet(active:true),[0xB2,9,1])
XCTAssertEqual(output.packet(active:false),[0xB2,9,100])
}
@MainActor func testMixtrackXMLUsesNativeAdapterAndSendsActualLEDOutput() async throws {
let profile = try XCTUnwrap(DJXMLProfile.bundled.first{$0.name == "Numark Mixtrack GO"})
XCTAssertEqual(profile.adapter,"Mixtrack Go")
XCTAssertNotNil(DJXMLProfile.match("Numark MixTrack Go Bluetooth",in:[profile]))
var client = MIDIClientRef(0),source = MIDIEndpointRef(0),destination = MIDIEndpointRef(0)
XCTAssertEqual(MIDIClientCreateWithBlock("KJMix LED test" as CFString,&client,nil),noErr)
defer { if source != 0 { MIDIEndpointDispose(source) };if destination != 0 { MIDIEndpointDispose(destination) };MIDIClientDispose(client) }
let name = "KJMix LED \(UUID())"
var received = [UInt8]()
XCTAssertEqual(MIDIDestinationCreateWithBlock(client,name as CFString,&destination) { packets,_ in
var bytes = [UInt8]()
var packet = UnsafeRawPointer(packets).advanced(by:MemoryLayout<MIDIPacketList>.offset(of:\.packet)!).assumingMemoryBound(to:MIDIPacket.self)
for _ in 0..<packets.pointee.numPackets {
let data = UnsafeRawPointer(packet).advanced(by:MemoryLayout<MIDIPacket>.offset(of:\.data)!)
bytes.append(contentsOf:UnsafeRawBufferPointer(start:data,count:Int(packet.pointee.length)));packet = UnsafePointer(MIDIPacketNext(packet))
}
let captured = bytes;Task { @MainActor in received += captured }
},noErr)
XCTAssertEqual(MIDISourceCreate(client,name as CFString,&source),noErr)
let suite = "KJMix.LED.\(UUID())",defaults = try XCTUnwrap(UserDefaults(suiteName:suite))
defer { defaults.removePersistentDomain(forName:suite) }
let midi = DJMIDI(defaults:defaults,catalog:[]);midi.refreshSources()
let input = try XCTUnwrap(midi.sources.first{$0.endpoint == source});midi.select(input.id);midi.applyXML(profile)
XCTAssertTrue(midi.mixtrackEnabled)
var actions = [String]();midi.onAction = { action,_,_ in actions.append(action) }
midi.receive([0x95,0,127,0x95,0x17,127]);XCTAssertTrue(actions.contains("B Loop 8"))
midi.updateFeedback(aPlaying:true,bPlaying:false,sync:true,aLoaded:true,bLoaded:true,cue:"B",aCues:[true,false,false,false])
try await Task.sleep(nanoseconds:100_000_000)
XCTAssertTrue((0..<max(0,received.count-2)).contains{Array(received[$0..<($0+3)]) == [0x90,0,127]})
XCTAssertTrue((0..<max(0,received.count-2)).contains{Array(received[$0..<($0+3)]) == [0x94,0x14,127]})
XCTAssertTrue((0..<max(0,received.count-6)).contains{Array(received[$0..<($0+7)]) == [0xF0,0,0x20,0x7F,3,1,0xF7]})
let initial = received.count
try await Task.sleep(nanoseconds:2_100_000_000)
XCTAssertGreaterThan(received.count,initial,"LED state is resent after the startup light show ends")
}
func testDjayXMLLoadsAllSuppliedProfilesAndReportsUnsupportedActions() throws {
XCTAssertEqual(DJXMLProfile.bundled.count,210)
let dj2go = try XCTUnwrap(DJXMLProfile.bundled.first{$0.name == "Numark DJ2GO"})