removed unwanted bar now with waveforms, major updates and enhancements

This commit is contained in:
Mark Gregory
2026-11-07 10:37:20 +00:00
parent a1c25cdb23
commit ec601f3544
6 changed files with 242 additions and 89 deletions
+55 -7
View File
@@ -2074,14 +2074,14 @@ extension KJamsCloneTests {
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)
var values = [6.0,3.0,-6.0,0.75]
let host = NSHostingView(rootView:HStack(spacing:0) { ForEach(0..<4,id:\.self) { i in DJKnob(label:["HIGH","MID","LOW","FILTER"][i],value:Binding(get:{values[i]},set:{values[i] = $0}),range:i == 3 ? -1...1 : -12...12,reset:0,color:.cyan).frame(width:80,height:80) } }.frame(width:320,height:80))
host.frame = NSRect(x:0,y:0,width:320,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 index in 0..<4 {
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)
@@ -2089,7 +2089,21 @@ extension KJamsCloneTests {
}
try await Task.sleep(nanoseconds:100_000_000)
}
XCTAssertEqual(values,[0,0,0])
XCTAssertEqual(values,[0,0,0,0])
func controls(_ view: NSView) -> [DJRotaryControl] { (view as? DJRotaryControl).map{[$0]} ?? view.subviews.flatMap{controls($0)} }
let knobs = controls(host)
XCTAssertEqual(knobs.count,4)
for knob in knobs {
let centre = knob.convert(NSPoint(x:knob.bounds.midX,y:14),to:nil)
let down = try XCTUnwrap(NSEvent.mouseEvent(with:.leftMouseDown,location:centre,modifierFlags:[],timestamp:ProcessInfo.processInfo.systemUptime,windowNumber:window.windowNumber,context:nil,eventNumber:0,clickCount:1,pressure:1))
knob.mouseDown(with:down)
let drag = try XCTUnwrap(NSEvent.mouseEvent(with:.leftMouseDragged,location:NSPoint(x:centre.x,y:centre.y+12),modifierFlags:[],timestamp:ProcessInfo.processInfo.systemUptime,windowNumber:window.windowNumber,context:nil,eventNumber:0,clickCount:1,pressure:1))
knob.mouseDragged(with:drag);XCTAssertNotEqual(knob.value,0)
let label = knob.convert(NSPoint(x:knob.bounds.midX,y:35),to:nil)
let doubleClick = try XCTUnwrap(NSEvent.mouseEvent(with:.leftMouseDown,location:label,modifierFlags:[],timestamp:ProcessInfo.processInfo.systemUptime,windowNumber:window.windowNumber,context:nil,eventNumber:0,clickCount:2,pressure:1))
knob.mouseDown(with:doubleClick);XCTAssertEqual(knob.value,0)
}
XCTAssertEqual(values,[0,0,0,0],"Reset on the label also updates each audio parameter after a drag")
}
}
@@ -2362,9 +2376,7 @@ extension KJamsCloneTests {
let libraryFrame = libraryScroll.convert(libraryScroll.bounds,to:host)
if host.isFlipped { XCTAssertGreaterThan(libraryFrame.maxY,host.bounds.maxY-50,"The library fills the bottom of the workspace") }
else { XCTAssertLessThan(libraryFrame.minY,host.bounds.minY+50,"The library fills the bottom of the workspace") }
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/dj-tab.png")) }
try await selectTab(0)
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/karaoke-tab.png")) }
}
}
@@ -3234,6 +3246,16 @@ extension KJamsCloneTests {
let window = NSWindow(contentRect:view.frame,styleMask:[.borderless],backing:.buffered,defer:false)
window.contentView = view;window.appearance = NSAppearance(named:.darkAqua);window.setFrameOrigin(NSPoint(x:-10000,y:-10000));window.orderFront(nil)
try await Task.sleep(nanoseconds:250_000_000);view.layoutSubtreeIfNeeded()
if size.width == 1320 {
system.a.player.filter = 0.63;system.b.player.filter = -0.31
try await Task.sleep(nanoseconds:60_000_000);view.layoutSubtreeIfNeeded()
func knobs(_ parent: NSView) -> [DJRotaryControl] { (parent as? DJRotaryControl).map{[$0]} ?? parent.subviews.flatMap{knobs($0)} }
let filters = knobs(view).filter{$0.label == "FILTER"}
XCTAssertEqual(filters.count,2)
XCTAssertTrue(filters.contains{abs($0.value-0.63) < 0.001});XCTAssertTrue(filters.contains{abs($0.value+0.31) < 0.001},"External audio-model changes update both mixer strips")
for filter in filters { filter.resetToDefault() }
XCTAssertEqual(system.a.player.filter,0);XCTAssertEqual(system.b.player.filter,0)
}
let rep = try XCTUnwrap(view.bitmapImageRepForCachingDisplay(in:view.bounds));view.cacheDisplay(in:view.bounds,to:rep)
let png = try XCTUnwrap(rep.representation(using:.png,properties:[:]))
let path = root.appendingPathComponent("DJ-Studio-\(Int(size.width)).png");try png.write(to:path)
@@ -3246,3 +3268,29 @@ extension KJamsCloneTests {
}
}
}
extension KJamsCloneTests {
@MainActor func testClearingDeckMarkersPreservesLoadedPlayingAudio() async throws {
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
defer { try? FileManager.default.removeItem(at:root) }
let fixture = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures/Demo.wav"))
let player = Player(waveformCacheDirectory:root,persistsMix:false)
defer { player.stop() };player.autoFade = false
try player.load(Song(id:7,title:"Markers",audio:fixture.path,cdg:nil,rating:0,plays:0),startPlaying:false)
player.setCue(at:0.4)
for index in 0..<4 { player.seek(0.2+Double(index)*0.1);player.hotCue(index) }
XCTAssertTrue(player.hotCues.allSatisfy{$0 != nil})
player.hotCue(2,clear:true);XCTAssertNil(player.hotCues[2]);XCTAssertNotNil(player.hotCues[0])
player.defineLoop(start:0.2,end:0.5)
for _ in 0..<100 where !player.nativeLoopActive { try await Task.sleep(nanoseconds:5_000_000) }
player.seek(0.25);player.toggle();try await Task.sleep(nanoseconds:80_000_000)
let position = player.displayTime
player.clearDeckMarkers()
XCTAssertEqual(player.currentSongID,7);XCTAssertTrue(player.playing);XCTAssertTrue(player.audioPlaybackStarted)
XCTAssertTrue(player.hotCues.allSatisfy{$0 == nil});XCTAssertEqual(player.cuePosition,0)
XCTAssertNil(player.loopIn);XCTAssertNil(player.loopOut);XCTAssertFalse(player.loopEnabled)
XCTAssertEqual(player.displayTime,position,accuracy:0.04,"Clearing markers must not jump back to the start")
try await Task.sleep(nanoseconds:160_000_000)
XCTAssertGreaterThan(player.displayTime,position+0.08)
}
}