performance updates
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
import QuartzCore
|
||||
|
||||
struct DJLoopDrag {
|
||||
enum Mode { case seek, start, end, move, create }
|
||||
@@ -155,6 +156,7 @@ struct DJNativeSpectrum: NSViewRepresentable {
|
||||
let showTimeGrid: Bool
|
||||
let spectralWaveforms: Bool
|
||||
func makeNSView(context: Context) -> DJNativeSpectrumView { DJNativeSpectrumView() }
|
||||
static func dismantleNSView(_ view: DJNativeSpectrumView,coordinator: ()) { view.stopDisplayClock() }
|
||||
func updateNSView(_ view: DJNativeSpectrumView,context: Context) {
|
||||
view.player = player;view.bpm = bpm;view.color = color;view.span = seconds
|
||||
view.label = label;view.showTimeGrid = showTimeGrid;view.spectralWaveforms = spectralWaveforms
|
||||
@@ -170,6 +172,10 @@ final class DJNativeSpectrumView: DJWaveMouseView {
|
||||
var label = ""
|
||||
var showTimeGrid = true,spectralWaveforms = true
|
||||
private var clock: Timer?
|
||||
private var displayClock: AnyObject?
|
||||
private var tiles = [Int:CGImage]()
|
||||
private var tileKey = ""
|
||||
private(set) var waveformTileBuildCount = 0
|
||||
private var frozenViewport: Double?
|
||||
private var hoverX: Double?
|
||||
private var tracking: NSTrackingArea?
|
||||
@@ -178,17 +184,32 @@ final class DJNativeSpectrumView: DJWaveMouseView {
|
||||
freeze = { [weak self] start in self?.frozenViewport = start;self?.needsDisplay = true }
|
||||
}
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
func stopDisplayClock() {
|
||||
clock?.invalidate();clock = nil
|
||||
if #available(macOS 14.0,*) { (displayClock as? CADisplayLink)?.invalidate() }
|
||||
displayClock = nil
|
||||
}
|
||||
override func viewDidMoveToWindow() {
|
||||
super.viewDidMoveToWindow();clock?.invalidate();clock = nil
|
||||
if window != nil {
|
||||
let timer = Timer(timeInterval:1.0/60,repeats:true) { [weak self] _ in
|
||||
guard let self,self.player?.playing == true,!self.isHiddenOrHasHiddenAncestor else { return }
|
||||
self.needsDisplay = true
|
||||
}
|
||||
super.viewDidMoveToWindow();stopDisplayClock()
|
||||
guard window != nil else { return }
|
||||
if #available(macOS 14.0,*) {
|
||||
let link = displayLink(target:self,selector:#selector(displayFrame(_:)))
|
||||
link.preferredFrameRateRange = CAFrameRateRange(minimum:30,maximum:120,preferred:60)
|
||||
link.add(to:.main,forMode:.common);displayClock = link
|
||||
} else {
|
||||
let timer = Timer(timeInterval:1.0/60,repeats:true) { [weak self] _ in self?.refreshFrame() }
|
||||
RunLoop.main.add(timer,forMode:.common);clock = timer
|
||||
}
|
||||
}
|
||||
deinit { clock?.invalidate() }
|
||||
@available(macOS 14.0,*) @objc private func displayFrame(_ link: CADisplayLink) { refreshFrame() }
|
||||
private func refreshFrame() {
|
||||
guard player?.playing == true,!isHiddenOrHasHiddenAncestor else { return }
|
||||
needsDisplay = true
|
||||
}
|
||||
deinit {
|
||||
clock?.invalidate()
|
||||
if #available(macOS 14.0,*) { (displayClock as? CADisplayLink)?.invalidate() }
|
||||
}
|
||||
override func updateTrackingAreas() {
|
||||
super.updateTrackingAreas();if let tracking { removeTrackingArea(tracking) }
|
||||
let area = NSTrackingArea(rect:.zero,options:[.activeInKeyWindow,.inVisibleRect,.mouseMoved,.mouseEnteredAndExited],owner:self)
|
||||
@@ -210,24 +231,7 @@ final class DJNativeSpectrumView: DJWaveMouseView {
|
||||
NSColor.white.withAlphaComponent(0.15).setFill()
|
||||
for tick in 0...8 { NSRect(x:Double(tick)/8*width,y:0,width:0.5,height:height).fill() }
|
||||
}
|
||||
let paths = (0..<32).map { _ in NSBezierPath() },count = max(1,Int(width/2))
|
||||
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 barHeight = max(1,amplitude*height*0.85)
|
||||
var bucket = 0
|
||||
if spectralWaveforms,player.waveformBands.indices.contains(index) {
|
||||
let band = player.waveformBands[index],sum = max(0.0001,band.low+band.mid+band.high)
|
||||
bucket = min(31,max(0,Int((Double(band.mid/sum)*0.38+Double(band.high/sum)*0.72)*31)))
|
||||
}
|
||||
paths[bucket].appendRect(NSRect(x:Double(i)*width/Double(count),y:(height-barHeight)/2,width:1.5,height:barHeight))
|
||||
}
|
||||
for bucket in paths.indices where !paths[bucket].isEmpty {
|
||||
(spectralWaveforms ? NSColor(calibratedHue:Double(bucket)/31,saturation:0.85,brightness:0.95,alpha:1) : color).setFill();paths[bucket].fill()
|
||||
}
|
||||
drawWaveform(player,start:start,width:width,height:height)
|
||||
if let bpm {
|
||||
for beat in DJWaveMarkers.beats(start:start,span:span,bpm:bpm,width:width) {
|
||||
let x = (Double(beat)*60/bpm-start)/span*width,major = beat % 4 == 0
|
||||
@@ -258,6 +262,48 @@ final class DJNativeSpectrumView: DJWaveMouseView {
|
||||
text(String(format:"%d:%05.2f",Int(seconds)/60,seconds.truncatingRemainder(dividingBy:60)),at:NSPoint(x:max(0,width-65),y:max(0,height-14)),color:.white)
|
||||
}
|
||||
}
|
||||
private func drawWaveform(_ player: Player,start: Double,width: Double,height: Double) {
|
||||
guard let context = NSGraphicsContext.current?.cgContext else { return }
|
||||
let scale = window?.backingScaleFactor ?? 2
|
||||
let key = "\(player.waveformRevision):\(width):\(height):\(scale):\(span):\(spectralWaveforms):\(color)"
|
||||
if tileKey != key { tiles.removeAll();tileKey = key }
|
||||
let first = Int(floor(start/span))
|
||||
tiles = tiles.filter { (first...first+2).contains($0.key) }
|
||||
context.interpolationQuality = .high
|
||||
for tile in first...first+2 {
|
||||
if tiles[tile] == nil { tiles[tile] = waveformTile(player,tile:tile,width:width,height:height,scale:scale) }
|
||||
if let image = tiles[tile] {
|
||||
let x = (Double(tile)*span-start)/span*width
|
||||
context.draw(image,in:CGRect(x:x,y:0,width:width,height:height))
|
||||
}
|
||||
}
|
||||
}
|
||||
private func waveformTile(_ player: Player,tile: Int,width: Double,height: Double,scale: Double) -> CGImage? {
|
||||
guard let context = CGContext(data:nil,width:max(1,Int(ceil(width*scale))),height:max(1,Int(ceil(height*scale))),bitsPerComponent:8,bytesPerRow:0,space:CGColorSpaceCreateDeviceRGB(),bitmapInfo:CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil }
|
||||
context.scaleBy(x:scale,y:scale)
|
||||
let paths = (0..<32).map { _ in CGMutablePath() },count = max(1,Int(width/2))
|
||||
for i in 0..<count {
|
||||
let seconds = Double(tile)*span+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 barHeight = max(1,amplitude*height*0.85)
|
||||
var bucket = 0
|
||||
if spectralWaveforms,player.waveformBands.indices.contains(index) {
|
||||
let band = player.waveformBands[index],sum = max(0.0001,band.low+band.mid+band.high)
|
||||
bucket = min(31,max(0,Int((Double(band.mid/sum)*0.38+Double(band.high/sum)*0.72)*31)))
|
||||
}
|
||||
paths[bucket].addRect(CGRect(x:Double(i)*width/Double(count),y:(height-barHeight)/2,width:1.5,height:barHeight))
|
||||
}
|
||||
for bucket in paths.indices where !paths[bucket].isEmpty {
|
||||
let shade = spectralWaveforms ? NSColor(calibratedHue:Double(bucket)/31,saturation:0.85,brightness:0.95,alpha:1) : color
|
||||
context.setFillColor(shade.cgColor);context.addPath(paths[bucket]);context.fillPath()
|
||||
}
|
||||
let image = context.makeImage()
|
||||
if image != nil { waveformTileBuildCount += 1 }
|
||||
return image
|
||||
}
|
||||
private func text(_ value: String,at point: NSPoint,color: NSColor) {
|
||||
(value as NSString).draw(at:point,withAttributes:[.font:NSFont.monospacedSystemFont(ofSize:9,weight:.semibold),.foregroundColor:color])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user