OSCQuery
the sketch's parameters published for a control app to find: an OSCQueryServer serves every @Param as a namespace over HTTP and takes their values over OSC at the same port number, both advertised on the network, so TouchOSC or Chataigne builds the controls itself; the left column draws the namespace as served (OSCQueryServer, OSCQueryNode)
- Run
swift run --package-path Examples Example-Integration-OSCQuery- Imports
OllinOSC- Source
- Examples/Integration/OSCQuery/Sketch.swift
import Foundation
import Ollin
import OllinOSC
/// The sketch's parameters, published for a control app to find. An
/// `OSCQueryServer` on the extension seam serves every `@Param` below as an
/// OSCQuery namespace: the tree as JSON over HTTP on port 9000, and the values
/// over OSC on UDP at the same number, both advertised on the local network
/// under the sketch's name. Open a control app that speaks OSCQuery on the
/// same Wi-Fi, pick this sketch from its list, and the controls build
/// themselves with the right ranges; move one and the ring answers.
///
/// With nothing but a browser, `http://<this-mac>.local:9000/` shows the tree,
/// and `curl` reads one node: `curl 'http://localhost:9000/Shape/radius?VALUE'`.
/// Any OSC sender works for the other direction, `/Shape/radius 240.0`.
///
/// The left column draws the namespace as served, so the picture and the
/// listing are the same thing seen twice.
@main
final class OSCQuery: Sketch {
enum Style: String, CaseIterable, ParamOption { case petals, rings, spokes }
@Param(20...400, group: "Shape") var radius = 180.0
@Param(3...24, group: "Shape") var count = 9
@Param(group: "Shape") var style = Style.petals
@Param(0...3, group: "Motion") var speed = 0.5
@Param(group: "Motion") var spin = true
@Param(x: 0...1, y: 0...1, style: .pad, group: "Motion") var pivot = Vector2(0.62, 0.5)
@Param(group: "Color") var tint = Color(red: 1.0, green: 0.62, blue: 0.24)
@Param(count: 2...6, group: "Color") var inks = Palette(
Color(red: 0.98, green: 0.36, blue: 0.30),
Color(red: 0.99, green: 0.80, blue: 0.30),
Color(red: 0.35, green: 0.72, blue: 0.95))
let query = OSCQueryServer()
override func setup() {
extend(query)
}
override func draw() {
background(Color(white: 0.06))
drawFigure()
drawNamespace()
fill(Color(white: 1).withAlpha(0.55))
textSize(20)
textAlign(.center)
drawText(query.url ?? "opening the namespace", width / 2, height - 36)
}
// MARK: The picture
private func drawFigure() {
let cx = pivot.x * width
let cy = pivot.y * height
let turn = spin ? time * speed : 0
let colors = inks.colors
noStroke()
withState {
translate(cx, cy)
rotate(turn)
for i in 0..<count {
let angle = Double(i) / Double(count) * .tau
let ink = colors[i % max(1, colors.count)].mixed(with: tint, 0.35)
withState {
rotate(angle)
switch style {
case .petals:
fill(ink.withAlpha(0.75))
drawEllipse(radius * 0.55, 0, radius * 0.9, radius * 0.32)
case .rings:
noFill()
stroke(ink.withAlpha(0.85))
strokeWeight(3)
drawCircle(radius * 0.6, 0, radius * 0.4)
case .spokes:
stroke(ink)
strokeWeight(6)
strokeCap(.round)
drawLine(radius * 0.15, 0, radius, 0)
}
}
}
}
noStroke()
fill(tint)
drawCircle(cx, cy, 14)
}
// MARK: The namespace, as served
private func drawNamespace() {
let root = query.namespace()
var y = 64.0
textAlign(.left)
textSize(22)
fill(Color(white: 0.92))
drawText(root.description ?? "", 40, y)
y += 40
textSize(16)
for node in root.methods {
let value = (node.value ?? []).map(describe).joined(separator: " ")
fill(Color(white: 0.75))
drawText(node.fullPath, 40, y)
fill(tint.withAlpha(0.9))
drawText(node.type ?? "", 300, y)
fill(Color(white: 0.55))
drawText(value, 340, y)
y += 26
}
}
private func describe(_ value: OSCQueryValue) -> String {
switch value {
case .number(let n): return n == n.rounded() ? "\(Int(n))" : String(format: "%.2f", n)
case .text(let s): return s
case .bool(let b): return b ? "true" : "false"
case .null: return "null"
}
}
}