Skip to content

Instantly share code, notes, and snippets.

@bradhowes
Last active September 28, 2024 04:37
Show Gist options
  • Save bradhowes/b9784b1ba6c3d93a6fead12f8a3138a4 to your computer and use it in GitHub Desktop.
Save bradhowes/b9784b1ba6c3d93a6fead12f8a3138a4 to your computer and use it in GitHub Desktop.
Swift playground that integrates a @published attribute and a AUParameter instance, allowing changes in either to affect the other.
import SwiftUI
import AVFoundation
import PlaygroundSupport
class Model: ObservableObject {
@Published var value: Double = 5.0 // sole source of truth
}
class ParamTree {
static let address: AUParameterAddress = 123
let parameter: AUParameter
let parameterTree: AUParameterTree
init(model: Model) {
parameter = AUParameterTree.createParameter(withIdentifier: "parameter", name: "Parameter",
address: Self.address, min: 0.0, max: 10.0,
unit: .generic, unitName: nil,
flags: [.flag_IsReadable, .flag_IsWritable],
valueStrings: nil, dependentParameters: nil)
parameter.value = AUValue(model.value)
parameterTree = AUParameterTree.createTree(withChildren: [parameter])
// Set closure to convert AUParameter values into String
parameterTree.implementorStringFromValueCallback = { param, value in
guard param.address == Self.address, let value = value else { return "?" }
return String(format: "%.2f", value.pointee)
}
// Set closure to change model value via an AUParameter
parameterTree.implementorValueObserver = { param, value in
guard param.address == Self.address else { return }
print("set parameter: \(value)")
model.value = Double(value)
}
// Set closure to provide values for an AUParameter
parameterTree.implementorValueProvider = { param in
guard param.address == Self.address else { return 0.0 }
print("get parameter: \(model.value)")
return AUValue(model.value)
}
}
}
extension AUParameter {
/// Obtain a ClosedRange using the `minValue` and `maxValue` attributes
var bounds: ClosedRange<AUValue> { minValue...maxValue }
}
extension Comparable {
/**
Force our value to be within the given closed range
- parameter range: the limits to apply
- returns: clamped value
*/
func clamped(to range: ClosedRange<Self>) -> Self { min(max(self, range.lowerBound), range.upperBound) }
}
struct ValueView: View {
@Binding var value: Double
var body: some View {
Text("Value: \(value)")
}
}
struct ContentView: View {
@ObservedObject var model: Model
let params: ParamTree
init(model: Model) {
self.model = model
self.params = ParamTree(model: model)
}
var body: some View {
VStack {
Text("Hello World")
ValueView(value: $model.value)
// Manipulate the model value
Slider(value: $model.value, in: Double(params.parameter.minValue)...Double(params.parameter.maxValue))
// Decrease the AU parameter by 1
Button("Down") {
guard let parameter = params.parameterTree.parameter(withAddress: 123) else { return }
print("Dn - parameter: \(parameter.value)")
print("Dn - model: \(model.value)")
let value = AUValue(parameter.value - 1.0).clamped(to: parameter.bounds)
parameter.setValue(value, originator: nil)
print("Dn - parameter: \(parameter.value)")
print("Dn - model: \(model.value)")
}
// Increase the AU parameter by 1
Button("Up") {
guard let parameter = params.parameterTree.parameter(withAddress: 123) else { return }
print("Up - parameter: \(parameter.value)")
print("Up - model: \(model.value)")
let value = AUValue(parameter.value + 1.0).clamped(to: parameter.bounds)
parameter.setValue(value, originator: nil)
print("Up - parameter: \(parameter.value)")
print("Up - model: \(model.value)")
}
}
}
}
let view = ContentView(model: Model())
PlaygroundPage.current.setLiveView(view)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment