Ollin

SyphonLoopback

publishes its own frames as a Syphon source and subscribes to them, so the recursive feedback-tunnel inset shows the round trip. Open Syphon's Simple Client to see the same frames in another app (publishSyphon, SyphonClient)

Run
swift run --package-path Examples Example-Integration-SyphonLoopback
Imports
OllinSyphon
Source
Examples/Integration/SyphonLoopback/Sketch.swift
import Ollin
import OllinSyphon

/// A full Syphon round-trip, on screen. The sketch runs *both* ends: it publishes
/// its own rendered frames as a Syphon source, and a `SyphonClient` subscribes to
/// that same source and draws what it receives back as an inset. The inset is the
/// frame making the trip out to Syphon and back — and because the inset is itself
/// part of the next published frame, you get an infinite-mirror video-feedback
/// tunnel, which is the round-trip made visible.
///
/// It's self-contained on purpose (like the `OSCLoopback` example): no second app
/// needed. To prove the *cross-app* path, run it and open Syphon's Simple Client
/// (or an `ofxSyphon` sketch, Resolume, MadMapper, …) — "Ollin Loopback" appears
/// as a source. To go the other way, run `SyphonViewer` and point it here.
@main
final class SyphonLoopback: Sketch {

    let sourceName = "Ollin Loopback"
    var feed: SyphonClient!
    let palette = CosinePalette.neon

    override func setup() {
        publishSyphon(name: sourceName)        // share every frame
        feed = SyphonClient(named: sourceName) // … and subscribe to ourselves
        noStroke()
    }

    override func draw() {
        background(Color(white: 0.05))

        // A lively main element so the picture isn't only mirrors: an orbiting
        // ring of dots plus a breathing core.
        let center = Vector2(width * 0.5, height * 0.5)
        let count = 12
        for i in 0..<count {
            let a = time * 0.6 + Double(i) / Double(count) * .tau
            let r = (180 + sin(time * 1.3 + Double(i)) * 60) * scale
            let p = center + Vector2(angle: a) * r
            fill(palette.color(at: Double(i) / Double(count)))
            drawCircle(center: p, radius: (10 + 8 * sin(time * 2 + Double(i))) * scale)
        }
        fill(Color(white: 1, alpha: 0.9))
        drawCircle(center: center, radius: (24 + 10 * sin(time * 2)) * scale)

        // Reconnect to our own source once it's been announced (the server starts
        // on the first published frame, so the client connects a frame or two in).
        if !feed.isConnected { feed.connect(named: sourceName) }

        // Draw the received frame as a slightly inset, slightly rotated panel — the
        // recursion makes a feedback tunnel that proves the loop is live.
        if let frame = feed.frame {
            withState {
                translate(center)
                rotate(0.04 * sin(time * 0.5))
                scale(0.82)
                translate(Vector2(-center.x, -center.y))
                drawImage(frame, in: Rectangle(x: 0, y: 0, width: width, height: height))
            }
        }

        fill(Color(white: 0.6))
        textSize(22 * scale)
        let status = feed.isConnected ? "live" : "connecting…"
        drawText("Syphon ↺ \"\(sourceName)\" — \(status)", 30 * scale, 50 * scale)
    }
}

↑↓ move ↩ open