Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merging visionos branch into main #87

Merged
merged 21 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/AudioKitUI/Controls/ADSRView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ extension CGPoint {
#else

import AVFoundation
import Cocoa
import AppKit

/// Call back for values for attack, decay, sustain, and release parameters
public typealias ADSRCallback = (AUValue, AUValue, AUValue, AUValue) -> Void
Expand Down
147 changes: 116 additions & 31 deletions Sources/AudioKitUI/Visualizations/FloatPlot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Metal
import MetalKit

// This must be in sync with the definition in shaders.metal
public struct FragmentConstants {
struct FragmentConstants {
public var foregroundColor: SIMD4<Float>
public var backgroundColor: SIMD4<Float>
public var isFFT: Bool
Expand All @@ -17,13 +17,15 @@ public struct FragmentConstants {
public var padding: Int = 0
}

public class FloatPlot: MTKView, MTKViewDelegate {
let waveformTexture: MTLTexture!
class FloatPlot: NSObject {
var waveformTexture: MTLTexture?
let commandQueue: MTLCommandQueue!
let pipelineState: MTLRenderPipelineState!
let bufferSampleCount: Int
var bufferSampleCount: Int
var dataCallback: () -> [Float]
var constants: FragmentConstants
let layerRenderPassDescriptor: MTLRenderPassDescriptor
let device = MTLCreateSystemDefaultDevice()

public init(frame frameRect: CGRect,
constants: FragmentConstants,
Expand All @@ -32,15 +34,6 @@ public class FloatPlot: MTKView, MTKViewDelegate {
self.constants = constants
bufferSampleCount = Int(frameRect.width)

let desc = MTLTextureDescriptor()
desc.textureType = .type1D
desc.width = Int(frameRect.width)
desc.pixelFormat = .r32Float
assert(desc.height == 1)
assert(desc.depth == 1)

let device = MTLCreateSystemDefaultDevice()
waveformTexture = device?.makeTexture(descriptor: desc)
commandQueue = device!.makeCommandQueue()

let library = try! device?.makeDefaultLibrary(bundle: Bundle.module)
Expand All @@ -51,7 +44,6 @@ public class FloatPlot: MTKView, MTKViewDelegate {
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.vertexFunction = vertexProgram
pipelineStateDescriptor.fragmentFunction = fragmentProgram
pipelineStateDescriptor.sampleCount = 1

let colorAttachment = pipelineStateDescriptor.colorAttachments[0]!
colorAttachment.pixelFormat = .bgra8Unorm
Expand All @@ -63,23 +55,45 @@ public class FloatPlot: MTKView, MTKViewDelegate {

pipelineState = try! device!.makeRenderPipelineState(descriptor: pipelineStateDescriptor)

super.init(frame: frameRect, device: device)

clearColor = .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 0)

delegate = self
layerRenderPassDescriptor = MTLRenderPassDescriptor()
layerRenderPassDescriptor.colorAttachments[0].loadAction = .clear
layerRenderPassDescriptor.colorAttachments[0].storeAction = .store
layerRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing Semicolon Violation: Lines should not have trailing semicolons. (trailing_semicolon)

}

@available(*, unavailable)
required init(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func resize(width: Int) {

if width == 0 {
return
}

let desc = MTLTextureDescriptor()
desc.textureType = .type1D
desc.width = width
desc.pixelFormat = .r32Float
assert(desc.height == 1)
assert(desc.depth == 1)

waveformTexture = device?.makeTexture(descriptor: desc)
bufferSampleCount = width

}

func updateWaveform(samples: [Float]) {
if samples.count == 0 {
return
}

guard let waveformTexture else {
print("⚠️ updateWaveform: waveformTexture is nil")
return
}

var resampled = [Float](repeating: 0, count: bufferSampleCount)

for i in 0 ..< bufferSampleCount {
Expand All @@ -97,24 +111,60 @@ public class FloatPlot: MTKView, MTKViewDelegate {
}
}

public func mtkView(_: MTKView, drawableSizeWillChange _: CGSize) {
// We may want to resize the texture.
func encode(to commandBuffer: MTLCommandBuffer, pass: MTLRenderPassDescriptor) {
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: pass) else { return }

encoder.setRenderPipelineState(pipelineState)
encoder.setFragmentTexture(waveformTexture, index: 0)
assert(MemoryLayout<FragmentConstants>.size == 48)
encoder.setFragmentBytes(&constants, length: MemoryLayout<FragmentConstants>.size, index: 0)
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
encoder.endEncoding()
}

public func draw(in view: MTKView) {
func draw(to layer: CAMetalLayer) {

updateWaveform(samples: dataCallback())

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)

let size = layer.drawableSize
let w = Float(size.width)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Identifier Name Violation: Variable name should be between 3 and 40 characters long: 'w' (identifier_name)

let h = Float(size.height)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Identifier Name Violation: Variable name should be between 3 and 40 characters long: 'h' (identifier_name)

// let scale = Float(view.contentScaleFactor)

if let commandBuffer = commandQueue.makeCommandBuffer() {
if let renderPassDescriptor = currentRenderPassDescriptor {
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { return }
if w == 0 || h == 0 {
return
}

guard let commandBuffer = commandQueue.makeCommandBuffer() else {
return
}

if let currentDrawable = layer.nextDrawable() {

layerRenderPassDescriptor.colorAttachments[0].texture = currentDrawable.texture

encoder.setRenderPipelineState(pipelineState)
encoder.setFragmentTexture(waveformTexture, index: 0)
assert(MemoryLayout<FragmentConstants>.size == 48)
encoder.setFragmentBytes(&constants, length: MemoryLayout<FragmentConstants>.size, index: 0)
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
encoder.endEncoding()
encode(to: commandBuffer, pass: layerRenderPassDescriptor)

commandBuffer.present(currentDrawable)
} else {
print("⚠️ couldn't get drawable")
}
commandBuffer.commit()
}
}

#if !os(visionOS)
extension FloatPlot: MTKViewDelegate {
public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
resize(width: Int(size.width))
}

public func draw(in view: MTKView) {
updateWaveform(samples: dataCallback())

if let commandBuffer = commandQueue.makeCommandBuffer() {
if let renderPassDescriptor = view.currentRenderPassDescriptor {
encode(to: commandBuffer, pass: renderPassDescriptor)
if let drawable = view.currentDrawable {
commandBuffer.present(drawable)
}
Expand All @@ -125,3 +175,38 @@ public class FloatPlot: MTKView, MTKViewDelegate {
}
}
}
#endif

#if !os(visionOS)
public class FloatPlotCoordinator {
var renderer: FloatPlot

init(renderer: FloatPlot) {
self.renderer = renderer
}

var view: MTKView {
let view = MTKView(frame: CGRect(x: 0, y: 0, width: 1024, height: 1024), device: renderer.device)
view.clearColor = .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 0)
view.delegate = renderer
return view
}
}
#else
public class FloatPlotCoordinator {
var renderer: FloatPlot

init(renderer: FloatPlot) {
self.renderer = renderer
}

var view: MetalView {
let view = MetalView(frame: CGRect(x: 0, y: 0, width: 1024, height: 1024))
view.renderer = renderer
view.metalLayer.pixelFormat = .bgra8Unorm
view.metalLayer.isOpaque = false
view.createDisplayLink()
return view
}
}
#endif
90 changes: 90 additions & 0 deletions Sources/AudioKitUI/Visualizations/MetalView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/

#if os(iOS) || os(visionOS)
import UIKit

class MetalView: UIView {

var renderer: FloatPlot?
var displayLink: CADisplayLink?

@objc static override var layerClass: AnyClass {
CAMetalLayer.self
}

var metalLayer: CAMetalLayer {
layer as! CAMetalLayer
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force Cast Violation: Force casts should be avoided. (force_cast)

}

func createDisplayLink() {
displayLink = CADisplayLink(target: self,
selector: #selector(render))

displayLink?.add(to: .current,
forMode: .default)
}

override func draw(_ rect: CGRect) {
render()
}

override func draw(_ layer: CALayer, in ctx: CGContext) {
render()
}

override func display(_ layer: CALayer) {
render()
}

@objc func render() {
guard let renderer else {
print("⚠️ no renderer")
return
}
renderer.draw(to: metalLayer)
}

func resizeDrawable() {

var newSize = bounds.size
newSize.width *= contentScaleFactor
newSize.height *= contentScaleFactor

if newSize.width <= 0 || newSize.height <= 0 {
return
}

if newSize.width == metalLayer.drawableSize.width &&
newSize.height == metalLayer.drawableSize.height {
return
}

metalLayer.drawableSize = newSize
renderer?.resize(width: Int(newSize.width))

setNeedsDisplay()
}

@objc override var frame: CGRect {
get { super.frame }
set {
super.frame = newValue
resizeDrawable()
}
}

@objc override func layoutSubviews() {
super.layoutSubviews()
resizeDrawable()
}

@objc override var bounds: CGRect {
get { super.bounds }
set {
super.bounds = newValue
resizeDrawable()
}
}

}
#endif
13 changes: 7 additions & 6 deletions Sources/AudioKitUI/Visualizations/NodeFFTView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Accelerate
import AudioKit
import AVFoundation
import SwiftUI
import MetalKit

public struct NodeFFTView: ViewRepresentable {
var nodeTap: FFTTap
Expand All @@ -13,7 +14,7 @@ public struct NodeFFTView: ViewRepresentable {
nodeTap = FFTTap(node, bufferSize: UInt32(bufferSampleCount), callbackQueue: .main) { _ in }
}

internal var plot: FloatPlot {
public func makeCoordinator() -> FloatPlotCoordinator {
nodeTap.start()

let constants = FragmentConstants(foregroundColor: Color.yellow.simd,
Expand All @@ -26,14 +27,14 @@ public struct NodeFFTView: ViewRepresentable {
nodeTap.fftData
}

return plot
return .init(renderer: plot)
}

#if os(macOS)
public func makeNSView(context: Context) -> FloatPlot { return plot }
public func updateNSView(_ nsView: FloatPlot, context: Context) {}
public func makeNSView(context: Context) -> NSView { return context.coordinator.view }
public func updateNSView(_ nsView: NSView, context: Context) {}
#else
public func makeUIView(context: Context) -> FloatPlot { return plot }
public func updateUIView(_ uiView: FloatPlot, context: Context) {}
public func makeUIView(context: Context) -> UIView { return context.coordinator.view }
public func updateUIView(_ uiView: UIView, context: Context) {}
#endif
}
17 changes: 10 additions & 7 deletions Sources/AudioKitUI/Visualizations/NodeOutputView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Accelerate
import AudioKit
import AVFoundation
import SwiftUI
import MetalKit

public struct NodeOutputView: ViewRepresentable {
private var nodeTap: RawDataTap
Expand All @@ -18,19 +19,21 @@ public struct NodeOutputView: ViewRepresentable {
nodeTap = RawDataTap(node, bufferSize: UInt32(bufferSize), callbackQueue: .main)
}

var plot: FloatPlot {
public func makeCoordinator() -> FloatPlotCoordinator {
nodeTap.start()

return FloatPlot(frame: CGRect(x: 0, y: 0, width: 1024, height: 1024), constants: constants) {
return nodeTap.data
let plot = FloatPlot(frame: CGRect(x: 0, y: 0, width: 1024, height: 1024), constants: constants) {
nodeTap.data
}

return .init(renderer: plot)
}

#if os(macOS)
public func makeNSView(context: Context) -> FloatPlot { return plot }
public func updateNSView(_ nsView: FloatPlot, context: Context) {}
public func makeNSView(context: Context) -> NSView { return context.coordinator.view }
public func updateNSView(_ nsView: NSView, context: Context) {}
#else
public func makeUIView(context: Context) -> FloatPlot { return plot }
public func updateUIView(_ uiView: FloatPlot, context: Context) {}
public func makeUIView(context: Context) -> UIView { return context.coordinator.view }
public func updateUIView(_ uiView: UIView, context: Context) {}
#endif
}
Loading
Loading