Skip to content

Commit 3559d8b

Browse files
maksutovicwthollidayaure
authored
Merging visionos branch into main (#87)
* Move code to extension * Don't have MTKView on VisionOS * Add encode function and draw(in:) * Resize waveformTexture * Use drawable size for width of waveformTexture * Move code out * FloatPlot shouldn't be an MTKView * Make private * Add MetalView * Add createDisplayLink * Logging * Add logging * visionOS support * uncomment display lnk * Update FloatPlot.swift * Fix warning * Fix warnings * Fix macOS build * visionOS support Co-Authored-By: Aurelius Prochazka <aure@aure.com> * FloatPloat now defaults to a clear background --------- Co-authored-by: Taylor Holliday <wtholliday@gmail.com> Co-authored-by: Aurelius Prochazka <aure@aure.com>
1 parent 63443b3 commit 3559d8b

File tree

7 files changed

+234
-52
lines changed

7 files changed

+234
-52
lines changed

Sources/AudioKitUI/Controls/ADSRView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ extension CGPoint {
502502
#else
503503

504504
import AVFoundation
505-
import Cocoa
505+
import AppKit
506506

507507
/// Call back for values for attack, decay, sustain, and release parameters
508508
public typealias ADSRCallback = (AUValue, AUValue, AUValue, AUValue) -> Void

Sources/AudioKitUI/Visualizations/FloatPlot.swift

Lines changed: 116 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Metal
55
import MetalKit
66

77
// This must be in sync with the definition in shaders.metal
8-
public struct FragmentConstants {
8+
struct FragmentConstants {
99
public var foregroundColor: SIMD4<Float>
1010
public var backgroundColor: SIMD4<Float>
1111
public var isFFT: Bool
@@ -17,13 +17,15 @@ public struct FragmentConstants {
1717
public var padding: Int = 0
1818
}
1919

20-
public class FloatPlot: MTKView, MTKViewDelegate {
21-
let waveformTexture: MTLTexture!
20+
class FloatPlot: NSObject {
21+
var waveformTexture: MTLTexture?
2222
let commandQueue: MTLCommandQueue!
2323
let pipelineState: MTLRenderPipelineState!
24-
let bufferSampleCount: Int
24+
var bufferSampleCount: Int
2525
var dataCallback: () -> [Float]
2626
var constants: FragmentConstants
27+
let layerRenderPassDescriptor: MTLRenderPassDescriptor
28+
let device = MTLCreateSystemDefaultDevice()
2729

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

35-
let desc = MTLTextureDescriptor()
36-
desc.textureType = .type1D
37-
desc.width = Int(frameRect.width)
38-
desc.pixelFormat = .r32Float
39-
assert(desc.height == 1)
40-
assert(desc.depth == 1)
41-
42-
let device = MTLCreateSystemDefaultDevice()
43-
waveformTexture = device?.makeTexture(descriptor: desc)
4437
commandQueue = device!.makeCommandQueue()
4538

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

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

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

66-
super.init(frame: frameRect, device: device)
67-
68-
clearColor = .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 0)
69-
70-
delegate = self
58+
layerRenderPassDescriptor = MTLRenderPassDescriptor()
59+
layerRenderPassDescriptor.colorAttachments[0].loadAction = .clear
60+
layerRenderPassDescriptor.colorAttachments[0].storeAction = .store
61+
layerRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0);
7162
}
7263

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

69+
func resize(width: Int) {
70+
71+
if width == 0 {
72+
return
73+
}
74+
75+
let desc = MTLTextureDescriptor()
76+
desc.textureType = .type1D
77+
desc.width = width
78+
desc.pixelFormat = .r32Float
79+
assert(desc.height == 1)
80+
assert(desc.depth == 1)
81+
82+
waveformTexture = device?.makeTexture(descriptor: desc)
83+
bufferSampleCount = width
84+
85+
}
86+
7887
func updateWaveform(samples: [Float]) {
7988
if samples.count == 0 {
8089
return
8190
}
8291

92+
guard let waveformTexture else {
93+
print("⚠️ updateWaveform: waveformTexture is nil")
94+
return
95+
}
96+
8397
var resampled = [Float](repeating: 0, count: bufferSampleCount)
8498

8599
for i in 0 ..< bufferSampleCount {
@@ -97,24 +111,60 @@ public class FloatPlot: MTKView, MTKViewDelegate {
97111
}
98112
}
99113

100-
public func mtkView(_: MTKView, drawableSizeWillChange _: CGSize) {
101-
// We may want to resize the texture.
114+
func encode(to commandBuffer: MTLCommandBuffer, pass: MTLRenderPassDescriptor) {
115+
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: pass) else { return }
116+
117+
encoder.setRenderPipelineState(pipelineState)
118+
encoder.setFragmentTexture(waveformTexture, index: 0)
119+
assert(MemoryLayout<FragmentConstants>.size == 48)
120+
encoder.setFragmentBytes(&constants, length: MemoryLayout<FragmentConstants>.size, index: 0)
121+
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
122+
encoder.endEncoding()
102123
}
103124

104-
public func draw(in view: MTKView) {
125+
func draw(to layer: CAMetalLayer) {
126+
105127
updateWaveform(samples: dataCallback())
128+
129+
let size = layer.drawableSize
130+
let w = Float(size.width)
131+
let h = Float(size.height)
132+
// let scale = Float(view.contentScaleFactor)
106133

107-
if let commandBuffer = commandQueue.makeCommandBuffer() {
108-
if let renderPassDescriptor = currentRenderPassDescriptor {
109-
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { return }
134+
if w == 0 || h == 0 {
135+
return
136+
}
137+
138+
guard let commandBuffer = commandQueue.makeCommandBuffer() else {
139+
return
140+
}
141+
142+
if let currentDrawable = layer.nextDrawable() {
143+
144+
layerRenderPassDescriptor.colorAttachments[0].texture = currentDrawable.texture
110145

111-
encoder.setRenderPipelineState(pipelineState)
112-
encoder.setFragmentTexture(waveformTexture, index: 0)
113-
assert(MemoryLayout<FragmentConstants>.size == 48)
114-
encoder.setFragmentBytes(&constants, length: MemoryLayout<FragmentConstants>.size, index: 0)
115-
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
116-
encoder.endEncoding()
146+
encode(to: commandBuffer, pass: layerRenderPassDescriptor)
117147

148+
commandBuffer.present(currentDrawable)
149+
} else {
150+
print("⚠️ couldn't get drawable")
151+
}
152+
commandBuffer.commit()
153+
}
154+
}
155+
156+
#if !os(visionOS)
157+
extension FloatPlot: MTKViewDelegate {
158+
public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
159+
resize(width: Int(size.width))
160+
}
161+
162+
public func draw(in view: MTKView) {
163+
updateWaveform(samples: dataCallback())
164+
165+
if let commandBuffer = commandQueue.makeCommandBuffer() {
166+
if let renderPassDescriptor = view.currentRenderPassDescriptor {
167+
encode(to: commandBuffer, pass: renderPassDescriptor)
118168
if let drawable = view.currentDrawable {
119169
commandBuffer.present(drawable)
120170
}
@@ -125,3 +175,38 @@ public class FloatPlot: MTKView, MTKViewDelegate {
125175
}
126176
}
127177
}
178+
#endif
179+
180+
#if !os(visionOS)
181+
public class FloatPlotCoordinator {
182+
var renderer: FloatPlot
183+
184+
init(renderer: FloatPlot) {
185+
self.renderer = renderer
186+
}
187+
188+
var view: MTKView {
189+
let view = MTKView(frame: CGRect(x: 0, y: 0, width: 1024, height: 1024), device: renderer.device)
190+
view.clearColor = .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 0)
191+
view.delegate = renderer
192+
return view
193+
}
194+
}
195+
#else
196+
public class FloatPlotCoordinator {
197+
var renderer: FloatPlot
198+
199+
init(renderer: FloatPlot) {
200+
self.renderer = renderer
201+
}
202+
203+
var view: MetalView {
204+
let view = MetalView(frame: CGRect(x: 0, y: 0, width: 1024, height: 1024))
205+
view.renderer = renderer
206+
view.metalLayer.pixelFormat = .bgra8Unorm
207+
view.metalLayer.isOpaque = false
208+
view.createDisplayLink()
209+
return view
210+
}
211+
}
212+
#endif
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/
2+
3+
#if os(iOS) || os(visionOS)
4+
import UIKit
5+
6+
class MetalView: UIView {
7+
8+
var renderer: FloatPlot?
9+
var displayLink: CADisplayLink?
10+
11+
@objc static override var layerClass: AnyClass {
12+
CAMetalLayer.self
13+
}
14+
15+
var metalLayer: CAMetalLayer {
16+
layer as! CAMetalLayer
17+
}
18+
19+
func createDisplayLink() {
20+
displayLink = CADisplayLink(target: self,
21+
selector: #selector(render))
22+
23+
displayLink?.add(to: .current,
24+
forMode: .default)
25+
}
26+
27+
override func draw(_ rect: CGRect) {
28+
render()
29+
}
30+
31+
override func draw(_ layer: CALayer, in ctx: CGContext) {
32+
render()
33+
}
34+
35+
override func display(_ layer: CALayer) {
36+
render()
37+
}
38+
39+
@objc func render() {
40+
guard let renderer else {
41+
print("⚠️ no renderer")
42+
return
43+
}
44+
renderer.draw(to: metalLayer)
45+
}
46+
47+
func resizeDrawable() {
48+
49+
var newSize = bounds.size
50+
newSize.width *= contentScaleFactor
51+
newSize.height *= contentScaleFactor
52+
53+
if newSize.width <= 0 || newSize.height <= 0 {
54+
return
55+
}
56+
57+
if newSize.width == metalLayer.drawableSize.width &&
58+
newSize.height == metalLayer.drawableSize.height {
59+
return
60+
}
61+
62+
metalLayer.drawableSize = newSize
63+
renderer?.resize(width: Int(newSize.width))
64+
65+
setNeedsDisplay()
66+
}
67+
68+
@objc override var frame: CGRect {
69+
get { super.frame }
70+
set {
71+
super.frame = newValue
72+
resizeDrawable()
73+
}
74+
}
75+
76+
@objc override func layoutSubviews() {
77+
super.layoutSubviews()
78+
resizeDrawable()
79+
}
80+
81+
@objc override var bounds: CGRect {
82+
get { super.bounds }
83+
set {
84+
super.bounds = newValue
85+
resizeDrawable()
86+
}
87+
}
88+
89+
}
90+
#endif

Sources/AudioKitUI/Visualizations/NodeFFTView.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Accelerate
44
import AudioKit
55
import AVFoundation
66
import SwiftUI
7+
import MetalKit
78

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

16-
internal var plot: FloatPlot {
17+
public func makeCoordinator() -> FloatPlotCoordinator {
1718
nodeTap.start()
1819

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

29-
return plot
30+
return .init(renderer: plot)
3031
}
3132

3233
#if os(macOS)
33-
public func makeNSView(context: Context) -> FloatPlot { return plot }
34-
public func updateNSView(_ nsView: FloatPlot, context: Context) {}
34+
public func makeNSView(context: Context) -> NSView { return context.coordinator.view }
35+
public func updateNSView(_ nsView: NSView, context: Context) {}
3536
#else
36-
public func makeUIView(context: Context) -> FloatPlot { return plot }
37-
public func updateUIView(_ uiView: FloatPlot, context: Context) {}
37+
public func makeUIView(context: Context) -> UIView { return context.coordinator.view }
38+
public func updateUIView(_ uiView: UIView, context: Context) {}
3839
#endif
3940
}

Sources/AudioKitUI/Visualizations/NodeOutputView.swift

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Accelerate
44
import AudioKit
55
import AVFoundation
66
import SwiftUI
7+
import MetalKit
78

89
public struct NodeOutputView: ViewRepresentable {
910
private var nodeTap: RawDataTap
@@ -18,19 +19,21 @@ public struct NodeOutputView: ViewRepresentable {
1819
nodeTap = RawDataTap(node, bufferSize: UInt32(bufferSize), callbackQueue: .main)
1920
}
2021

21-
var plot: FloatPlot {
22+
public func makeCoordinator() -> FloatPlotCoordinator {
2223
nodeTap.start()
2324

24-
return FloatPlot(frame: CGRect(x: 0, y: 0, width: 1024, height: 1024), constants: constants) {
25-
return nodeTap.data
25+
let plot = FloatPlot(frame: CGRect(x: 0, y: 0, width: 1024, height: 1024), constants: constants) {
26+
nodeTap.data
2627
}
28+
29+
return .init(renderer: plot)
2730
}
2831

2932
#if os(macOS)
30-
public func makeNSView(context: Context) -> FloatPlot { return plot }
31-
public func updateNSView(_ nsView: FloatPlot, context: Context) {}
33+
public func makeNSView(context: Context) -> NSView { return context.coordinator.view }
34+
public func updateNSView(_ nsView: NSView, context: Context) {}
3235
#else
33-
public func makeUIView(context: Context) -> FloatPlot { return plot }
34-
public func updateUIView(_ uiView: FloatPlot, context: Context) {}
36+
public func makeUIView(context: Context) -> UIView { return context.coordinator.view }
37+
public func updateUIView(_ uiView: UIView, context: Context) {}
3538
#endif
3639
}

0 commit comments

Comments
 (0)