broken changes
This commit is contained in:
@@ -1703,7 +1703,7 @@ extension KJamsCloneTests {
|
||||
XCTAssertTrue(system.a.player.audioPlaybackStarted);XCTAssertTrue(system.b.player.audioPlaybackStarted)
|
||||
XCTAssertGreaterThan(system.a.player.time,0.05);XCTAssertGreaterThan(system.b.player.time,0.55)
|
||||
system.a.bpm = 120;system.a.bpmConfirmed = true;system.b.bpm = 126;system.b.bpmConfirmed = true
|
||||
system.sync(system.a);XCTAssertEqual(system.a.player.tempo,1.05,accuracy:0.001)
|
||||
system.sync(system.a);XCTAssertTrue(system.syncActive);XCTAssertEqual(system.a.player.tempo,1,accuracy:0.001);XCTAssertEqual(system.b.player.tempo,120.0/126,accuracy:0.001)
|
||||
system.a.player.stopDeck();XCTAssertTrue(system.b.player.playing)
|
||||
system.startPreview(system.b);XCTAssertNotNil(system.error);XCTAssertNil(system.previewDeck);XCTAssertFalse(system.preview.playing)
|
||||
}
|
||||
@@ -1747,6 +1747,7 @@ extension KJamsCloneTests {
|
||||
try await capturePreferences("DJ","General","/private/tmp/kjmix-dj-settings-general.png")
|
||||
try await capturePreferences("DJ","Sound","/private/tmp/kjmix-dj-settings-sound.png")
|
||||
try await capturePreferences("DJ","Appearance","/private/tmp/kjmix-dj-settings-appearance.png")
|
||||
model.dj.sync(model.dj.a)
|
||||
let console = NSHostingView(rootView:DJContinuousConsole(system:model.dj,loadA:{},loadB:{}).frame(width:1320,height:390).foregroundStyle(.white).preferredColorScheme(.dark))
|
||||
console.frame = NSRect(x:0,y:0,width:1320,height:390)
|
||||
let consoleWindow = NSWindow(contentRect:console.frame,styleMask:[.borderless],backing:.buffered,defer:false)
|
||||
@@ -1769,6 +1770,11 @@ extension KJamsCloneTests {
|
||||
let midi = DJMIDI(defaults:defaults);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))
|
||||
do {
|
||||
let restored = DJMIDI(defaults:defaults)
|
||||
XCTAssertEqual(restored.sourceID,input.id,"A fresh MIDI session restores the saved controller")
|
||||
}
|
||||
var actions = [String]();midi.onAction = { action,_,_ in actions.append(action) }
|
||||
func send(_ packets: [[UInt8]]) throws {
|
||||
var list = MIDIPacketList()
|
||||
@@ -1787,6 +1793,8 @@ extension KJamsCloneTests {
|
||||
try await Task.sleep(nanoseconds:80_000_000)
|
||||
XCTAssertEqual(actions,["A Play"])
|
||||
midi.select(0)
|
||||
XCTAssertNil(defaults.object(forKey:"DJ.MIDISource.v1"))
|
||||
XCTAssertEqual(DJMIDI(defaults:defaults).sourceID,0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1910,7 +1918,7 @@ extension KJamsCloneTests {
|
||||
let system = DJSystem(storageURL:root);system.master = 0
|
||||
defer { system.a.player.stop();system.b.player.stop() }
|
||||
system.load(first,into:"A");system.a.player.toggle()
|
||||
system.load(second,into:"A");XCTAssertNotNil(system.error);XCTAssertEqual(system.a.song?.id,1)
|
||||
system.load(second,into:"A");XCTAssertNotNil(system.pendingReplacement);XCTAssertEqual(system.a.song?.id,1);system.pendingReplacement = nil
|
||||
system.a.player.stopDeck();system.error = nil
|
||||
system.settings.autoPlayOnLoad = true;system.settings.resetEQOnLoad = true
|
||||
system.settings.tempoRange = 8;system.settings.crossfaderCurve = "Linear";system.settings.headroomDB = 6
|
||||
@@ -1923,3 +1931,141 @@ extension KJamsCloneTests {
|
||||
XCTAssertEqual(restored.settings,system.settings)
|
||||
}
|
||||
}
|
||||
|
||||
extension KJamsCloneTests {
|
||||
@MainActor func testLinkedSyncUsesFirstDeckTogglesBothAndRestoresOriginalTempos() async throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
defer { try? FileManager.default.removeItem(at:root) }
|
||||
let system = DJSystem(storageURL:root);system.master = 0
|
||||
defer { system.a.unload();system.b.unload() }
|
||||
let file = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures/Demo.mp3"))
|
||||
let song = Song(id:1,title:"Sync",audio:file.path,cdg:nil,rating:0,plays:0)
|
||||
system.load(song,into:"A");system.load(song,into:"B")
|
||||
system.a.bpm = 128;system.b.bpm = 120;system.a.bpmConfirmed = true;system.b.bpmConfirmed = true
|
||||
system.a.player.tempo = 1.04;system.b.player.tempo = 0.96
|
||||
system.sync(system.b)
|
||||
XCTAssertTrue(system.syncActive);XCTAssertEqual(system.a.player.tempo,1.04,accuracy:0.001)
|
||||
XCTAssertEqual(system.b.bpm*system.b.player.tempo,128*1.04,accuracy:0.001)
|
||||
system.a.player.tempo = 1.02;try await Task.sleep(nanoseconds:40_000_000)
|
||||
XCTAssertEqual(system.b.bpm*system.b.player.tempo,128*1.02,accuracy:0.001)
|
||||
system.sync(system.a)
|
||||
XCTAssertFalse(system.syncActive);XCTAssertEqual(system.a.player.tempo,1.04,accuracy:0.001);XCTAssertEqual(system.b.player.tempo,0.96,accuracy:0.001)
|
||||
}
|
||||
@MainActor func testDJLoopActuallyRepeatsAndExplicitCueDoesNotMovePlayback() async throws {
|
||||
let deck = DJChannel("A");defer { deck.unload() };deck.master = 0
|
||||
let file = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures/Demo.mp3"))
|
||||
try deck.load(.init(id:1,title:"Loop",audio:file.path,cdg:nil,rating:0,plays:0))
|
||||
deck.bpm = 120;deck.bpmConfirmed = true;deck.loopBeats = 1
|
||||
deck.player.seek(0.2);deck.beatLoop();deck.player.toggle()
|
||||
try await Task.sleep(nanoseconds:900_000_000)
|
||||
XCTAssertTrue(deck.player.playing);XCTAssertTrue(deck.player.loopEnabled);XCTAssertGreaterThanOrEqual(deck.player.time,0.19);XCTAssertLessThan(deck.player.time,0.8)
|
||||
deck.beatLoop();XCTAssertFalse(deck.player.loopEnabled)
|
||||
deck.player.stopDeck();deck.player.setCue(at:1.2345)
|
||||
XCTAssertEqual(deck.player.cuePosition,1.2345,accuracy:0.0001);XCTAssertEqual(deck.player.time,0,accuracy:0.001)
|
||||
deck.player.returnToCue();XCTAssertEqual(deck.player.time,1.2345,accuracy:0.001)
|
||||
deck.player.defineLoop(start:deck.player.duration-0.2,end:deck.player.duration);deck.player.seek(deck.player.duration-0.2);deck.player.toggle()
|
||||
try await Task.sleep(nanoseconds:500_000_000)
|
||||
XCTAssertTrue(deck.player.playing,"Loop ending at EOF must restart instead of finishing")
|
||||
}
|
||||
func testBPMEstimationRecognisesPulseTrainAndRejectsSilence() throws {
|
||||
let envelope = (0..<2400).map { $0 % 96 < 4 ? 1.0 : 0.02 }
|
||||
XCTAssertEqual(try XCTUnwrap(DJBPM.estimate(envelope)),125,accuracy:0.5)
|
||||
XCTAssertNil(DJBPM.estimate(Array(repeating:0,count:2400)))
|
||||
XCTAssertNil(DJBPM.estimate(Array(repeating:0.5,count:2400)))
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
try FileManager.default.createDirectory(at:root,withIntermediateDirectories:true);defer { try? FileManager.default.removeItem(at:root) }
|
||||
let format = try XCTUnwrap(AVAudioFormat(standardFormatWithSampleRate:8000,channels:1)),buffer = try XCTUnwrap(AVAudioPCMBuffer(pcmFormat:format,frameCapacity:96000))
|
||||
buffer.frameLength = 96000;let samples = try XCTUnwrap(buffer.floatChannelData)[0]
|
||||
for i in 0..<96000 { samples[i] = i % 3840 < 40 ? 0.8 : 0 }
|
||||
let path = root.appendingPathComponent("125 BPM.wav")
|
||||
do { let output = try AVAudioFile(forWriting:path,settings:format.settings);try output.write(from:buffer) }
|
||||
XCTAssertEqual(try XCTUnwrap(DJBPM.analyse(path)),125,accuracy:1)
|
||||
}
|
||||
@MainActor func testFinderDropWarnsBeforeImportAndAllowsExplicitOverride() async throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
defer { try? FileManager.default.removeItem(at:root) }
|
||||
let system = DJSystem(storageURL:root);system.master = 0;defer { system.a.unload() }
|
||||
let file = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures/Demo.mp3"))
|
||||
try system.a.load(.init(id:999,title:"Playing",audio:file.path,cdg:nil,rating:0,plays:0));system.a.player.toggle()
|
||||
let provider = NSItemProvider()
|
||||
provider.registerDataRepresentation(forTypeIdentifier:UTType.fileURL.identifier,visibility:.all) { completion in completion(Data(file.absoluteString.utf8),nil);return nil }
|
||||
XCTAssertTrue(system.acceptDrop([provider],into:"A"))
|
||||
for _ in 0..<100 { if system.pendingReplacement != nil { break };try await Task.sleep(nanoseconds:10_000_000) }
|
||||
XCTAssertNotNil(system.pendingReplacement,system.error ?? system.status);XCTAssertTrue(system.a.player.playing);XCTAssertTrue(system.songs.isEmpty)
|
||||
let captured = try XCTUnwrap(system.pendingReplacement);system.pendingReplacement = nil
|
||||
system.confirmReplacement(captured)
|
||||
for _ in 0..<300 { if !system.importing { break };try await Task.sleep(nanoseconds:10_000_000) }
|
||||
XCTAssertNil(system.error);XCTAssertNil(system.pendingReplacement);XCTAssertEqual(system.songs.count,1)
|
||||
XCTAssertNotEqual(system.a.song?.id,999);XCTAssertFalse(system.a.player.playing)
|
||||
}
|
||||
@MainActor func testArtworkSelectsTrackJPGAndLoadsFromOriginalFolder() async throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
try FileManager.default.createDirectory(at:root,withIntermediateDirectories:true);defer { try? FileManager.default.removeItem(at:root) }
|
||||
let image = NSImage(size:NSSize(width:8,height:8));image.lockFocus();NSColor.red.setFill();NSBezierPath(rect:NSRect(x:0,y:0,width:8,height:8)).fill();image.unlockFocus()
|
||||
let bitmap = try XCTUnwrap(NSBitmapImageRep(data:try XCTUnwrap(image.tiffRepresentation)))
|
||||
let jpg = try XCTUnwrap(bitmap.representation(using:.jpeg,properties:[:]))
|
||||
try jpg.write(to:root.appendingPathComponent("Cover.jpg"));try jpg.write(to:root.appendingPathComponent("Song.JPG"))
|
||||
let audio = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures/Demo.mp3"))
|
||||
var song = Song(id:1,title:"Art",audio:audio.path,cdg:nil,rating:0,plays:0);song.originalPath = root.appendingPathComponent("Song.mp3").path
|
||||
XCTAssertEqual(DJTrackInfo.sidecar(song)?.lastPathComponent,"Song.JPG")
|
||||
let artwork = await DJTrackInfo.artwork(song);XCTAssertNotNil(artwork)
|
||||
}
|
||||
func testMixtrackGoPresetUsesActualProtocol14BitTempoAndPadModes() {
|
||||
var protocolState = MixtrackGoProtocol()
|
||||
let preset = MixtrackGoProtocol.mappings(source:123)
|
||||
XCTAssertTrue(preset.contains{$0.status == 0x91 && $0.number == 0 && $0.action == "B Play"})
|
||||
XCTAssertTrue(preset.contains{$0.status == 0xBF && $0.number == 8 && $0.action == "Crossfader"})
|
||||
XCTAssertEqual(protocolState.consume(.init(status:0xB0,data1:0x09,data2:64))?.count,0)
|
||||
XCTAssertEqual(protocolState.consume(.init(status:0xB0,data1:0x29,data2:0))?.first?.value,0.5)
|
||||
XCTAssertEqual(protocolState.consume(.init(status:0x94,data1:0x14,data2:127))?.first?.action,"A Hot cue 1")
|
||||
_ = protocolState.consume(.init(status:0x95,data1:0,data2:127))
|
||||
XCTAssertEqual(protocolState.consume(.init(status:0x95,data1:0x17,data2:127))?.first?.action,"B Loop 8")
|
||||
XCTAssertEqual(protocolState.consume(.init(status:0x95,data1:0x1F,data2:127))?.first?.action,"B Loop off")
|
||||
let leds = protocolState.feedback(aPlaying:true,bPlaying:false,sync:true,aLoaded:true,bLoaded:true,cue:nil)
|
||||
XCTAssertEqual(Array(leds.prefix(9)),[0x90,0,127,0x90,1,1,0x90,2,127])
|
||||
}
|
||||
@MainActor func testEQAndFilterDoubleClickActuallyResetKnobs() async throws {
|
||||
var values = [6.0,-6.0,0.75]
|
||||
let host = NSHostingView(rootView:HStack(spacing:0) { ForEach(0..<3,id:\.self) { i in DJKnob(label:["HIGH","LOW","FILTER"][i],value:Binding(get:{values[i]},set:{values[i] = $0}),range:i == 2 ? -1...1 : -12...12,reset:0,color:.cyan).frame(width:80,height:80) } }.frame(width:240,height:80))
|
||||
host.frame = NSRect(x:0,y:0,width:240,height:80)
|
||||
let window = NSWindow(contentRect:host.frame,styleMask:[.borderless],backing:.buffered,defer:false);window.contentView = host
|
||||
let previous = NSApp.keyWindow;window.setFrameOrigin(NSPoint(x:-10000,y:-10000));window.orderFront(nil);window.makeKey()
|
||||
defer { window.orderOut(nil);window.contentView = nil;previous?.makeKey() }
|
||||
try await Task.sleep(nanoseconds:150_000_000);host.layoutSubtreeIfNeeded()
|
||||
for index in 0..<3 {
|
||||
for click in 1...2 {
|
||||
for type in [NSEvent.EventType.leftMouseDown,.leftMouseUp] {
|
||||
let event = try XCTUnwrap(NSEvent.mouseEvent(with:type,location:NSPoint(x:40+index*80,y:46),modifierFlags:[],timestamp:ProcessInfo.processInfo.systemUptime,windowNumber:window.windowNumber,context:nil,eventNumber:0,clickCount:click,pressure:type == .leftMouseDown ? 1 : 0));NSApp.sendEvent(event)
|
||||
}
|
||||
}
|
||||
try await Task.sleep(nanoseconds:100_000_000)
|
||||
}
|
||||
XCTAssertEqual(values,[0,0,0])
|
||||
}
|
||||
}
|
||||
|
||||
extension KJamsCloneTests {
|
||||
@MainActor func testMixtrackGoInstalledPresetReceivesTempoPadsAndIgnoresButtonReleases() throws {
|
||||
let suite = "KJMix.Mixtrack.Tests.\(UUID().uuidString)",defaults = try XCTUnwrap(UserDefaults(suiteName:suite))
|
||||
defer { defaults.removePersistentDomain(forName:suite) }
|
||||
let midi = DJMIDI(defaults:defaults,connect:false);midi.installMixtrackGo()
|
||||
var events = [(String,Double)]()
|
||||
midi.onAction = { name,value,_ in if name != "Controller feedback" { events.append((name,value)) } }
|
||||
midi.receive([0x90,0,127,0x90,0,0,0x80,0,0])
|
||||
midi.receive([0xB0,0x09,64,0xB0,0x29,0])
|
||||
midi.receive([0x95,0,127,0x95,0,0,0x95,0x17,127])
|
||||
XCTAssertEqual(events.map{$0.0},["A Play","A Tempo","B Loop 8"])
|
||||
XCTAssertEqual(events[1].1,0.5,accuracy:0.0001)
|
||||
XCTAssertTrue(DJMIDI(defaults:defaults,connect:false).mixtrackEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
extension KJamsCloneTests {
|
||||
func testDJWaveBeatGuidesUseSourceBPMAndLimitOverviewDensity() {
|
||||
XCTAssertEqual(DJWaveMarkers.beats(start:0,span:2,bpm:120,width:800),[0,1,2,3,4])
|
||||
XCTAssertEqual(DJWaveMarkers.beats(start:1.1,span:1,bpm:120,width:800),[3,4])
|
||||
XCTAssertTrue(DJWaveMarkers.beats(start:0,span:8,bpm:nil,width:800).isEmpty)
|
||||
XCTAssertTrue(DJWaveMarkers.beats(start:0,span:8,bpm:0,width:800).isEmpty)
|
||||
XCTAssertLessThanOrEqual(DJWaveMarkers.beats(start:0,span:7200,bpm:180,width:500).count,101)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user