Skip to content

Instantly share code, notes, and snippets.

@Ivorforce
Created February 28, 2018 13:55
Show Gist options
  • Save Ivorforce/5a5e08b42437f2bb8fc8c52e8e285e85 to your computer and use it in GitHub Desktop.
Save Ivorforce/5a5e08b42437f2bb8fc8c52e8e285e85 to your computer and use it in GitHub Desktop.
//
// TrackSpectrumView.swift
// TenTunes
//
// Created by Lukas Tenbrink on 17.02.18.
// Copyright © 2018 ivorius. All rights reserved.
//
import Cocoa
import QuartzCore
func lerp(_ left: [CGFloat], _ right: [CGFloat], _ amount: CGFloat) -> [CGFloat] {
return zip(left, right).map { (cur, dest) in
return cur * (CGFloat(1.0) - amount) + dest * amount
}
}
class TrackSpectrumView: NSControl {
var location: Double? {
didSet {
self.setNeedsDisplay()
}
}
var _barLayers: [CALayer] = []
var _positionLayer = CALayer()
var analysis: Analysis? = nil
var _curValues: [[CGFloat]] = Array(repeating: Array(repeating: CGFloat(0), count: Analysis.sampleCount), count: 4)
var timer: Timer? = nil
var updateTime = 1.0 / 20.0
var lerpRatio = CGFloat(1.0 / 5.0)
override func awakeFromNib() {
postsFrameChangedNotifications = true
NotificationCenter.default.addObserver(self, selector: #selector(updateBounds), name: NSView.frameDidChangeNotification, object: self)
self.wantsLayer = true
let bgLayer = CAGradientLayer()
bgLayer.colors = [
NSColor.black.withAlphaComponent(0.4),
NSColor.clear
]
// bgLayer = CATransform3DMakeRotation(CGFloat.pi / 2, 0, 0, 1)
self.layer = bgLayer
_positionLayer = CALayer()
_positionLayer.backgroundColor = CGColor.white
self.layer!.addSublayer(_positionLayer)
// Initial update bars
self.resize(withOldSuperviewSize: self.bounds.size)
self.timer = Timer.scheduledTimer(withTimeInterval: updateTime, repeats: true) { _ in
let drawValues = self.analysis?.values ?? Array(repeating: Array(repeating: CGFloat(0), count: Analysis.sampleCount), count: 4)
self._curValues = (0..<4).map { lerp(self._curValues[$0], drawValues[$0], self.lerpRatio) }
self.updateLayers()
}
}
@objc func updateBounds() {
_positionLayer.frame = CGRect(x: 0, y: 0, width: 1, height: bounds.height)
// Clean Up
for layer in _barLayers { layer.removeFromSuperlayer() }
let barWidth = 2
let segmentWidth = barWidth + 2
let numBars = Int(self.bounds.width / CGFloat(segmentWidth))
let start = bounds.minX + (bounds.width - CGFloat(numBars * segmentWidth)) / 2
_barLayers = Array(0..<numBars).map { idx in
let bar = CALayer()
bar.zPosition = -1
bar.actions = [
"onOrderIn": NSNull(),
"onOrderOut": NSNull(),
"sublayers": NSNull(),
"contents": NSNull(),
"bounds": NSNull()
]
bar.frame = CGRect(
x: start + CGFloat(idx * segmentWidth) + 1,
y: self.bounds.minY,
width: CGFloat(barWidth),
height: 1 // Changed in update
)
self.layer!.addSublayer(bar)
return bar
}
updateLayers()
}
func updateLayers() {
let numBars = _barLayers.count
let values = _curValues.map { wf in wf.remap(toSize: numBars) }
let waveform = values[0], lows = values[1], mids = values[2], highs = values[3]
for run in 0...1 {
if run == 0 {
CATransaction.begin()
CATransaction.setAnimationDuration(updateTime)
}
for (idx, bar) in _barLayers.enumerated() {
// Height
let h = waveform[idx]
bar.frame.size.height = CGFloat(h * self.bounds.height)
// Color
let low = lows[idx] * lows[idx], mid = mids[idx] * mids[idx], high = highs[idx] * highs[idx]
let val = low + mid + high
// Don't go the full way so we don't loop back to red
bar.backgroundColor = NSColor(hue: (mid / val / 2 + high / val) * 0.8, saturation: CGFloat(0.3), brightness: CGFloat(0.8), alpha: CGFloat(1.0)).cgColor
}
if let location = location {
_positionLayer.frame.origin.x = CGFloat(location) * self.bounds.width
_positionLayer.isHidden = false
}
else {
_positionLayer.isHidden = true
}
if run == 0 {
CATransaction.commit()
}
}
}
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
return true
}
func click(at: NSPoint) {
self.location = (0.0...1.0).clamp(Float(at.x) / Float(self.bounds.width))
if let action = self.action, let target = self.target {
NSApp.sendAction(action, to: target, from: self)
}
}
override func mouseDragged(with event: NSEvent) {
self.click(at: self.convert(event.locationInWindow, from: nil))
}
override func mouseDown(with event: NSEvent) {
self.click(at: self.convert(event.locationInWindow, from: nil))
}
}
import AudioKit
import AudioKitUI
extension TrackSpectrumView {
func setBy(player: AKPlayer) {
if player.audioFile != nil {
self.setBy(time: player.currentTime, max: player.duration)
}
else {
self.location = nil
}
}
func setBy(time: Double, max: Double) {
self.location = time / max
}
func getBy(player: AKPlayer) -> Double? {
return player.audioFile != nil ? self.getBy(max: player.duration) : nil
}
func getBy(max: Double) -> Double? {
return self.location != nil ? self.location! * max : nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment