performance updates

This commit is contained in:
Mark Gregory
2026-11-06 07:44:08 +00:00
parent d71c823ee6
commit 9e610bd6f2
6 changed files with 182 additions and 144 deletions
+24 -37
View File
@@ -942,23 +942,26 @@ private struct DJPlatter: View {
}
}
}
private struct DJWaveform: View {
struct DJWaveform: View {
@State private var frozenStart: Double?
@ObservedObject var player: Player
let color: Color
var bpm: Double?
var body: some View {
TimelineView(.animation(minimumInterval:1.0/60,paused:!player.playing)) { _ in
let displayTime = player.displayTime
GeometryReader { geometry in
Canvas { context,size in
let values = player.waveform
guard !values.isEmpty else { return }
let count = max(1,Int(size.width/2))
var played = Path(),remaining = Path()
for i in 0..<count {
let index = min(values.count-1,i*values.count/count),height = max(1,Double(values[index])*size.height)
context.fill(Path(CGRect(x:Double(i)*size.width/Double(count),y:(size.height-height)/2,width:1.4,height:height)),with:.color(color.opacity(Double(i)/Double(count) < displayTime/max(0.001,player.duration) ? 0.4 : 0.9)))
let rect = CGRect(x:Double(i)*size.width/Double(count),y:(size.height-height)/2,width:1.4,height:height)
if Double(i)/Double(count) < displayTime/max(0.001,player.duration) { played.addRect(rect) } else { remaining.addRect(rect) }
}
context.fill(played,with:.color(color.opacity(0.4)))
context.fill(remaining,with:.color(color.opacity(0.9)))
DJWaveMarkers.draw(context:&context,size:size,start:0,span:player.duration,bpm:bpm,loop:player.loopEnabled ? player.loopIn.flatMap { a in player.loopOut.map { a...$0 } } : nil)
let x = displayTime/max(0.001,player.duration)*size.width
@@ -967,7 +970,6 @@ private struct DJWaveform: View {
.overlay { if player.waveformLoading { ProgressView().controlSize(.mini) } else if player.waveformUnavailable { Text("Waveform unavailable").font(.caption2).foregroundStyle(.secondary) } }
.modifier(DJLoopWaveInteraction(player:player,start:0,span:player.duration,width:geometry.size.width,frozenStart:$frozenStart))
}.accessibilityLabel("Track waveform and position").accessibilityValue(DJStyle.stamp(player.time)).accessibilityAdjustableAction { player.jog($0 == .increment ? 1 : -1) }
}
}
}
private struct DJMappingSheet: View {
@@ -1226,41 +1228,26 @@ private struct DJCentreWaveform: View {
@ObservedObject var player: Player
let letter: String
let color: Color
private var bpm: Double? { deck.bpmConfirmed ? deck.bpm : nil }
var settings = DJPreferences()
@State private var frozenStart: Double?
var body: some View {
TimelineView(.animation(minimumInterval:1.0/60,paused:!player.playing)) { _ in
let displayTime = player.displayTime
GeometryReader { geometry in
Canvas { context,size in
guard !player.waveform.isEmpty,player.duration > 0 else { return }
let span = settings.waveformSeconds,start = frozenStart ?? (displayTime-span/2),count = max(1,Int(size.width/2))
for second in 0...8 {
let x = Double(second)/8*size.width
context.fill(Path(CGRect(x:x,y:0,width:0.5,height:size.height)),with:.color(.white.opacity(settings.showTimeGrid ? 0.15 : 0)))
}
for i in 0..<count {
let seconds = start+Double(i)/Double(count)*span
guard seconds >= 0,seconds < player.duration else { continue }
let sample = min(Double(player.waveform.count-1),seconds/player.duration*Double(player.waveform.count))
let index = Int(sample),next = min(player.waveform.count-1,index+1),fraction = sample-Double(index)
let amplitude = Double(player.waveform[index])*(1-fraction)+Double(player.waveform[next])*fraction
let height = max(1,amplitude*size.height*0.85)
var shade = color
if settings.spectralWaveforms,player.waveformBands.indices.contains(index) {
let band = player.waveformBands[index],sum = max(0.0001,band.low+band.mid+band.high)
shade = Color(hue:Double(band.mid/sum)*0.38+Double(band.high/sum)*0.72,saturation:0.85,brightness:0.95)
}
context.fill(Path(CGRect(x:Double(i)*size.width/Double(count),y:(size.height-height)/2,width:1.5,height:height)),with:.color(shade))
}
DJWaveMarkers.draw(context:&context,size:size,start:start,span:span,bpm:bpm,loop:player.loopEnabled ? player.loopIn.flatMap { a in player.loopOut.map { a...$0 } } : nil)
let playhead = (displayTime-start)/span*size.width
context.fill(Path(CGRect(x:playhead,y:0,width:1.5,height:size.height)),with:.color(.red))
}.overlay(alignment:.topLeading) { Text(letter+" · \(Int(settings.waveformSeconds)) s").font(.system(size:9,weight:.semibold)).foregroundStyle(color).padding(5) }
.modifier(DJLoopWaveInteraction(player:player,start:displayTime-settings.waveformSeconds/2,span:settings.waveformSeconds,width:geometry.size.width,frozenStart:$frozenStart))
}.accessibilityLabel("Deck \(letter) scrolling waveform").accessibilityValue(DJStyle.stamp(player.time))
}
DJScrollingSpectrum(player:player,bpm:deck.bpmConfirmed ? deck.bpm : nil,color:color,seconds:settings.waveformSeconds,label:letter,accessibilityName:"Deck \(letter) scrolling waveform",showTimeGrid:settings.showTimeGrid,spectralWaveforms:settings.spectralWaveforms)
}
}
// The same audio-clock drawing and native mouse interaction serve both studios.
struct DJScrollingSpectrum: View {
@ObservedObject var player: Player
var bpm: Double?
var color: Color = .cyan
var seconds: Double = 16
var label = "KARAOKE"
var accessibilityName = "Karaoke scrolling waveform"
var showTimeGrid = true
var spectralWaveforms = true
var body: some View {
DJNativeSpectrum(player:player,bpm:bpm,color:NSColor(color),seconds:seconds,label:label,showTimeGrid:showTimeGrid,spectralWaveforms:spectralWaveforms)
.accessibilityLabel(accessibilityName).accessibilityValue(DJStyle.stamp(player.time))
.help("Hold the left button and drag left to advance or right to rewind. Shift-drag creates a loop; drag green loop edges to resize or its centre to move it.")
}
}