* docs(plans): action-head v3 + branch sync notes Update plan header : - v2 (Task 18) + v3 (Task 19) extensions chronology - Studio train validated, ckpt action_head_v3.pt landed - Mesh NaN-guard debug trail (commit4e7101c) - Branch convergence main == feat/action-head - Pointers to memories project_action_head_v3, etc. * feat(av-live): openpos 3D + DINO reid + filter Three improvements wired end-to-end: 1. Openpos 3D skeleton visible: Skeleton3DRenderer attached to a RealityKit AnchorEntity in BodyView, toggled by showSkeleton or vizMode==9. PoseOSCListener now parses /pose3d/count and /pose3d/kp (plus restored /face/* and /hand/* paths). 2. DINO re-id (dinov2_vits14, ~9 ms ANE forward): MeshRigger combines Hungarian IoU with cosine similarity over a per-pid embedding history (deque maxlen=10), weighted by MULTIHMR_REID_ALPHA (default 0.5). Falls back to pure IoU if DINO mlpackage absent or scipy missing. state.last_frame_rgb buffer added so the rigger can crop bbox regions for embedding. 3. PoseFilterChain on pose_world_landmarks: median (anti-spike) -> Kalman constant-velocity -> 50 ms lookahead -> IK elbow/knee/ankle clamp. Configurable via POSE_FILTER env (default median+kalman+lookahead+ik). <2 ms per frame for typical 1-2 persons. Tests: 5 new in test_dino_reid.py + 6 new in test_pose_filter.py, all green. Live validated by user: skeleton spawns, mesh stays stable. * fix(av-live-body): restore face+hand+3D (f540158) Three regressions after recent merges, all restored to match the originalf540158design: 1. FaceHandOverlay was no longer instantiated in ContentView. Added back as a SwiftUI Canvas overlay (68 dlib face landmarks with mouth slots 48-67, plus 21x2 hand landmarks cyan/magenta). 2. Skeleton3DRenderer was not attached. BodyView now creates an AnchorEntity at (0,0,-2.5), instantiates Skeleton3DRenderer and ties its visibility to vizMode==9 or showSkeleton toggle. 3. Joint and bone radii bumped to 4.5 cm / 2.2 cm so the 3D skeleton actually reads as 3D instead of looking flat. MeshRenderer exposes pelvisWorld map per pid for future interconnect uses (not auto-applied -- design keeps mesh and skeleton each in their own coord space perf540158). * feat(av-live): wireframe skel + face/hand filter Skeleton3DRenderer now renders a wireframe: joint radius 1 mm (quasi-invisible), bone radius 3 mm (line-like). Replaces the chunky bead armature with a clean filaire silhouette covering body 33 joints + face 68 dlib + hands 21x2, all 3D. FaceHandOverlay 2D Canvas removed from ContentView -- face and hand landmarks now live in the same 3D RealityKit armature as the body skeleton (Skeleton3DRenderer.applyFace / applyHands, anchored on nose joint 0 + wrist joints 15/16). pose_filter.py extended with FaceFilterChain (alpha-beta + 30 ms lookahead) and HandFilterChain. multi.py wires them after the 2D smoothers, plus ghost rejection (POSE_GHOST_MIN_VISIBLE), bbox NMS (POSE_NMS_IOU), and pid hysteresis. 10 new tests, all green. CoreML perf audit (bench_multihmr_coreml.py): predict() = 99% of wall-time on FP32. ANE catastrophic for DINOv2 (1300 ms), INT8 weight quant = no live gain (GPU compute-bound). 6.4-6.8 fps live is the hardware ceiling on this model. quantize_multihmr_int8.py left in scripts/ for future trials. * deps(icp): add open3d optional extra + smoke test Context: Task 1 of the ICP LiDAR <-> SMPL-X fusion plan needs a point-cloud library to align iPhone LiDAR scans with Multi-HMR SMPL-X meshes. Open3D's CPU-only ICP is sufficient at the 5-10 Hz LiDAR cadence. Approach: Add a dedicated `lidar` optional-dep group so the heavy dependency stays opt-in. Pin Python to 3.12 implicitly via the regenerated uv.lock because open3d 0.18-0.19 only ships cp311/cp312 wheels (cp314 absent). Smoke test guards future regressions. Changes: - pyproject.toml: new `lidar` extra with `open3d>=0.18,<0.20` - uv.lock: regenerated with open3d 0.19 + transitive deps (scikit-learn, scipy, dash stack, etc.) - tests/test_open3d_smoke.py: two-test smoke suite (PointCloud roundtrip + ICP convergence on translated copy), gated by `pytest.importorskip("open3d")` Impact: Unlocks subsequent ICP fusion tasks (LiDAR ingest, mesh alignment, transform publication) without forcing open3d on contributors who only run the base pose pipeline. * feat(icp): LiDAR TCP frame decoder + tests * feat(icp): LiDAR TCP socket reader with reconnect * feat(icp): extrinsic dataclass + JSON persistence * feat(icp): Kabsch + calibration CLI scaffold * feat(state): persons_arkit_joints fields * feat(viz): ARKit 91 -> MP 33 joint map * feat(viz): iphone OSC listener :57128 * feat(viz): arkit_fuse stage overrides 14 slots * feat(viz): arkit pelvis z locks cam translation * feat(viz): iphone OSC listener auto-start * docs: arkit fusion env vars * feat(icp): point-to-plane register + reject gate * feat(icp): partition LiDAR per pid by max-dist * feat(icp): FusionWorker + State.lidar_points * feat(icp): wire fusion thread behind ICP_FUSION Task 9 of the ICP LiDAR plan: integrate the FusionWorker built in earlier tasks into the live data_only_viz pipeline without disturbing the existing ARKit pelvis fuse path or the Multi-HMR worker thread. A new IcpFusionThread pulls LiDAR frames from LidarTCPReader, stages them into State, and applies in-place ICP registration on state.persons_smplx[*].vertices_3d. It runs as a separate daemon thread parallel to MultiHMRWorker rather than inline per frame — the autonomous-worker architecture didn't fit the plan's per-frame call site, so we adapted to a polling thread at 8 Hz. Activation is opt-in via ICP_FUSION=1 plus ICP_LIDAR_HOST; the default code path is untouched. Shutdown wired through applicationWillTerminate_. MultiHMRWorker.predict_once is added as a documented stub (NotImplementedError) because the existing PyTorch run loop is too coupled to the camera and MPS lifecycle for a clean single-shot extraction. calibrate_lidar.py keeps its placeholder until a follow-up refactor extracts a pure _infer(rgb) helper. * test(icp): synthetic latency + convergence bench * docs(icp): env vars + calibration procedure * docs(plans): icp lidar mesh + arkit joints Two complementary fusion plans landed in parallel on 2026-05-14: - iphone-lidar-multihmr-fusion : ARKit 91 joints -> MP33 fuse stage + pelvis z override (already implemented in 7 commits) - icp-lidar-smplx-fusion : LiDAR mesh point-to-plane ICP onto SMPL-X 10475 verts (12 tasks executed via subagent-driven-development) Both paths coexist; joints are sparse+fast (60 Hz), mesh is dense+slow (5-10 Hz). See docs/ICP_FUSION.md for the integration topology. * feat(icp): predict_once via CoreML backend * feat(av-live-body): wire ArkitOSCListener :57129 Receives /body3d/kp from iPhone ARBodyTracker on the diagnostic port (57129, distinct from Python's 57128 fuse input). Plumbed through ContentView -> BodyView -> Skeleton3DRenderer so the ARKit joints can be overlayed alongside Multi-HMR mesh. * feat(ios): iphone ARBodyTracker swiftpm app iOS 17+ Swift Package app (.swiftpm) streaming ARKit body joints via OSC UDP to two destinations: :57128 -> data_only_viz/iphone_osc_listener.py :57129 -> launcher/AV-Live-Body ArkitOSCListener.swift Features: - ARBodyTrackingConfiguration + sceneDepth (LiDAR) when supported - 91 joints per body, /body3d/kp pid joint_idx x y z - 30 fps throttle - SwiftUI UI: Host/Port fields, Start/Stop, live joints-per-second - Inline OSC encoder (no external dep) Env mesh (TCP :5500) NOT yet implemented; requires a separate ARWorldTrackingConfiguration session. ICP fusion path runs on bench data only until phase 2. * feat(data-feeds): 10 open-data OSC publisher * feat(viz): DataFeeds OSC listener + HUD * chore: gitignore tweaks * docs: network topology + mDNS hostnames Add a "Network topology" section to top-level CLAUDE.md doc the 3-host layout (GrosMac source, Supra-M1 sink via mDNS, iPhone via Personal Hotspot DHCP). mDNS is canonical now : AVBODY_HOST and MULTIHMR_REMOTE_HOST accept hostname.local instead of IPs, so the cluster survives DHCP rotations on iPhone hotspot (172.20.10.x). * fix(ios): add NSLocalNetworkUsageDescription iOS 14+ silently blocks UDP to LAN addresses without this key. The first time the app tries to send to 192.168.0.159, iOS will prompt the user to allow Local Network Access; the prompt must be accepted or the OSC stream never reaches the Mac. Also adds NSBonjourServices declaring _osc._udp so the system treats the connection as a recognised service. * docs: network topology + gitignore hygiene - CLAUDE.md: add mDNS hostname table (grosmac.local, supra-m1.local, iPhone hotspot 172.20.10.x). AVBODY_HOST / MULTIHMR_REMOTE_HOST accept hostnames — resilient to DHCP rotation. - .gitignore: exclude .remember/ tool state and iCloud '* 2' collision artifacts. * feat(ios): ARBody skeleton2D + overlay preview ARBodySession: publish 2D-projected skeleton snapshot for live overlay rendering on the iPhone screen alongside the camera feed. ContentView: SkeletonOverlay drawing on top of the AR view, with mock T-pose for Xcode previews (useMockBackground, useMockSkeleton). * docs: iPhone USB body-tracking link design Brainstormed design for replacing the OSC/network iPhone-Mac link with a wired USB transport via usbmuxd. iPhone streams ARKit skeleton + HEVC video; macOS app runs Multi-HMR CoreML and renders the mesh. Network-free, single native macOS app. * docs: iPhone USB transport plan (1 of 3) Bite-sized TDD plan for the network-free USB byte-pipe: shared AVLiveWire frame format, native usbmux client, iOS TCP frame server, incremental stream demuxer. * feat(avlivewire): shared wire package skeleton * feat(avlivewire): fixed 19-byte frame header codec Add FrameHeader, a fixed-size binary record so the demuxer can frame and resync the iPhone USB stream. Layout is big-endian: 4-byte magic AVL1, tag u8, pid i16, timestamp f64, length u32. The magic prefix lets a reader detect and skip corrupt bytes. Decoding rejects short buffers and bad magic by returning nil. Big-endian append/parse helpers are added as Data/UInt extensions to keep the codec self-contained. * chore: ignore SwiftPM .build artifacts Both AVLiveWire and AV-Live-Body produce .build/ on swift test; ignore them so they never get accidentally staged. * feat(avlivewire): skeleton and video codecs Add SkeletonPayload (91 ARKit joints + per-joint validity) and VideoPayload (one HEVC access unit + keyframe flag) with big-endian encode/decode. Reuses Task 2 Data/UInt32 helpers. * feat(avlivewire): incremental stream demuxer Add StreamDemuxer that accepts arbitrary byte chunks from a non-frame-aligned stream and emits complete (FrameHeader, Data) frames, resyncing on the magic prefix after corruption. * fix(avlivewire): cap demuxer payload length A corrupt header with a huge UInt32 length made feed buffer forever waiting for bytes that never arrive. Add an 8 MB max payload cap; a header exceeding it is treated as corrupt, its magic is skipped, and the demuxer resyncs on the next frame. * feat(av-live-body): usbmux message codec Add USBMuxProtocol, a codec for Apple's usbmuxd request/response protocol: a 16-byte little-endian header (length, version=1, message=8 plist, tag) followed by an XML property list. Wire an AVLiveBodyTests test target into Package.swift (none existed) so swift test runs the round-trip and header coverage. * feat(av-live-body): usbmux device client Add USBClient for usbmux device discovery and connect-to-port, with an injectable MuxTransport so tests need no real device. Harden USBMuxProtocol.readLE32 to return an optional with a bounds check, avoiding an out-of-range crash on truncated data. * feat(av-live-body): usbmuxd unix socket transport Add UnixMuxTransport, the production MuxTransport that opens a blocking AF_UNIX socket to /var/run/usbmuxd. Implements framed packet reads (4-byte LE length prefix) and raw stream reads for the tunneled byte stream after a successful Connect. * fix(av-live-body): harden unix socket transport Apply four code-review fixes to UnixMuxTransport: - send() now loops on partial writes and retries on EINTR instead of discarding write(2)'s return value. - Add deinit and an fd = -1 sentinel so close() is idempotent and the descriptor cannot leak. - precondition guards strcpy against sun_path overflow. - readN() distinguishes EOF from error and retries EINTR. * feat(ios): USB TCP frame server Add USBServer: an NWListener on a fixed local TCP port that usbmuxd tunnels to the tethered Mac. Sends AVLiveWire frames and exposes a connection-state callback. * build: depend on shared AVLiveWire package Both ARBodyTracker (iOS) and AVLiveBody (macOS) now depend on the local shared/AVLiveWire package so the wire format is defined once. iOS USBServer imports it; macOS use lands in Plan 3. * build(ios): add AVLiveWire package to xcodegen The xcodegen project did not declare the shared AVLiveWire package, so USBServer.swift would fail to import it in the generated Xcode project. Add it as a local package dep. * test(avlivewire): end-to-end chunked loopback Feeds 20 framed skeleton payloads through StreamDemuxer in 7-byte chunks (worst-case TCP fragmentation). Fixed a split range operator from the plan that did not parse. * fix(ios): guard USBServer listener and payload Report .idle (not .listening) when NWListener creation fails, and drop payloads larger than the demuxer's 8 MB cap so the receiver never silently discards an oversized frame. * chore: ignore .swiftpm editor state dirs swift test / Xcode create hidden .swiftpm dirs inside packages; ignore them so they never get staged. * docs: iPhone capture plan (2 of 3) Plan for HEVC video capture (VideoToolbox) over the USB transport and removal of the legacy OSC sender. Skeleton USB path already exists; this adds the video half. * feat(ios): HEVC video capture, drop OSC Add VideoEncoder (VideoToolbox HEVC) and stream encoded frames over USB as .video AVLiveWire frames alongside the skeleton. Remove the legacy OSC/UDP fanout and its host/port config UI — the iPhone link is now USB-only. * docs(ios): refresh stale OSC references ARBodySession header comment and Info.plist usage strings still described the removed OSC/UDP path; update them to the USB transport and drop the dead _osc._udp Bonjour service. * docs: macOS USB consumer plan (3a of 3) Plan for consuming the iPhone USB stream in AVLiveBody: USBSkeletonConsumer, VideoDecoder, 91-joint skeleton render. Multi-HMR dense mesh deferred to Plan 3b. * feat(av-live-body): USB skeleton consumer Background usbmux read loop feeding StreamDemuxer; republishes .skeleton frames as 91-joint ArkitBodyFrames and forwards .video payloads. Removed stale iCloud collision duplicate source files that broke the build. * fix(data-only): CoreML Multi-HMR usage bugs The CoreML Multi-HMR model was fine; the "0 detections" bug was caller-side. Add ImageNet normalization in infer() (the DINOv2 backbone needs it; raw [0,1] input collapsed all scores) and update stale hardcoded output var names to match the re-converted mlpackage. Bump the latency test threshold to the realistic ~140 ms full-model figure. * feat(av-live-body): HEVC video decoder VTDecompressionSession decoder for .video VideoPayloads. Rebuilds the format description from the parameter sets prepended to keyframe payloads by the iOS VideoEncoder. * feat(av-live-body): render 91-joint USB skeleton Complete the long-standing TODO: draw the 91 ARKit/USB skeleton joints as yellow markers, fed from lastArkit. Spawn entity trees for ARKit-only pids so the USB skeleton shows without a MediaPipe pose. * feat(av-live-body): wire USB consumer to renderer ContentView owns and starts a USBSkeletonConsumer, threaded through BodyView into Skeleton3DRenderer.attach. The renderer subscribes its $bodies into lastArkit, so the iPhone's USB skeleton drives the on-screen 91-joint markers. * docs: macOS Multi-HMR mesh plan (3b of 3) Final plan: bundle the validated FP32 mlpackage, MultiHMRCoreML Swift wrapper, BodyFusion (ARKit depth correction), mesh pipeline wiring. Completes the spec. * docs: AVLiveBody macOS rewrite design Clean-rewrite spec: fresh native macOS Xcode app for the iPhone-USB body pipeline. Reuses the tested USB components, single RealityKit scene (video quad + skeleton + mesh), drops all legacy MediaPipe/viz/data-feed code. * docs: AVLiveBody macOS rewrite plan 10-task plan: scaffold the xcodegen app, migrate the USB pipeline, build the RealityKit scene (video quad, skeleton, mesh), wire it, archive the legacy app. * feat(avlivebody-mac): scaffold xcode app Add an empty buildable native macOS app generated via xcodegen, sibling of iphone-arbody. Depends on the shared AVLiveWire package. Later tasks add the USB pipeline and RealityKit scene. * feat(avlivebody-mac): migrate usb transport Context: the new native AVLiveBody app needs the proven iPhone-Mac usbmux transport layer. These files are self-contained, depending only on AVLiveWire plus Apple system frameworks, so they cross the rewrite boundary unchanged. Approach: copy the three transport files and their unit tests byte-for-byte from launcher/AV-Live-Body, then make the test target buildable. Changes: - Add usb/USBMuxProtocol.swift, usb/USBClient.swift and usb/VideoDecoder.swift under Sources/AVLiveBody. - Add USBMuxProtocolTests.swift and USBClientTests.swift under Tests/AVLiveBodyTests. - Set GENERATE_INFOPLIST_FILE=YES on the AVLiveBodyTests target so xcodebuild can code sign the now-populated test bundle. Impact: the usbmux pipeline is available in the rewrite and its six unit tests run green under xcodebuild test. * feat(avlivebody-mac): usb skeleton consumer Add a cleaned USBSkeletonConsumer that publishes SkeletonPayload keyed by pid and owns video decoding directly, dropping the legacy ArkitOSCListener conversion layer. * fix(avlivebody-mac): guard thread store with lock Move the `thread` property write inside the stateLock-held region in start(); t.start() stays outside since the thread cannot run before start() is called. Removes a latent race. * feat(avlivebody-mac): multi-hmr and body fusion Context: Task 4 of the macOS rewrite needs the dense-mesh half of the pipeline alongside the USB skeleton consumer landed in task 3. Approach: Add a CoreML wrapper that mirrors the validated Python reference (data_only_viz/multihmr_coreml.py) and a pure-logic fusion stage that corrects the mesh pelvis depth using the LiDAR-precise USB skeleton. Changes: - MultiHMRCoreML.swift: 1x3x672x672 ImageNet-normalized image input, 1x3x3 cam_K input, K=4 SMPL-X person outputs at 10475 vertices, det threshold 0.3. - BodyFusion.swift: stateless fuse(persons, skeletons) overrides the highest-score mesh translation.z with the skeleton pelvis Z when available, passes through otherwise. - BodyFusionTests.swift: pelvis override and pass-through cases. Impact: Unlocks the mesh renderer wiring in later tasks and gives the macOS app metrically-correct depth in front of the camera. * fix(avlivebody-mac): load mlmodelc, clarify fusion Xcode compiles .mlpackage resources to .mlmodelc at build time; look up the compiled artifact directly and drop the redundant MLModel.compileModel step. Also rewrite BodyFusion docstring to match actual single-person pelvis-z override behaviour. * feat(avlivebody-mac): scene controller + view RealityKit scene plumbing: SceneController owns ARView, orbital camera, and holders for VideoQuad/SkeletonEntity/MeshEntity. SceneView is the SwiftUI NSViewRepresentable bridge. Build intentionally deferred to T8 (holder types land in T6-T8). * fix(avlivebody): orbit gesture + setUp guard Filter NSPanGestureRecognizer state in OrbitTarget.handlePan to dispatch only on .changed, replacing the Task wrapper with MainActor.assumeIsolated. Guard SceneController.setUp() with a didSetUp flag so duplicate makeNSView calls do not re-install gestures or re-add anchors. * feat(avlivebody-mac): 91-joint skeleton entity Yellow marker spheres pooled per pid; ARKit (x,y,z) -> RealityKit (x,-y,-z). Adapted .systemYellow to NSColor for macOS RealityKit Material.Color. Build deferred to T8. * feat(avlivebody-mac): video quad Flat 1.6x0.9m plane at z=-2m, textured per-frame from CVPixelBuffer via CIImage -> CGImage -> TextureResource. Per-frame TextureResource creation is the known perf hot spot, isolated here for later LowLevelTexture upgrade. * fix(avlivebody-mac): appkit import for orbit NSPanGestureRecognizer lives in AppKit on macOS; without the import the AVLiveBody module failed to emit. T5 leftover surfaced once T6/T7/T8 made the target compilable. * feat(avlivebody-mac): smpl-x mesh entity Render SMPL-X dense meshes (10475 verts) from Multi-HMR with pooled ModelEntity per person. Triangle indices loaded from the bundled smplx_faces.bin (flat UInt32 triplets, copied from the legacy launcher target). xcodegen folder-scanning bundles the .bin under Contents/Resources/ — no project.yml change needed. * feat(avlivebody-mac): wire scene + status bar Replace placeholder window with ContentView wiring USBSkeletonConsumer, SceneController, MultiHMRCoreML and BodyFusion per the T9 dataflow plan. * chore: archive legacy AV-Live-Body Superseded by avlivebody-mac/ on 2026-05-18. See docs/superpowers/specs/2026-05-18-avlivebody-macos-rewrite-design.md for the rewrite design and rationale. * fix(avlivebody): break onVideoFrame retain cycle Capture consumer weakly in the onVideoFrame closure so the USBSkeletonConsumer can be deallocated and its background thread exits cleanly. Guard the mesh-fusion path when consumer is gone. * fix(launcher): disable body spawn post-archive Legacy SwiftPM target archived to launcher/_archive-AV-Live-Body/. New native Xcode app lives at avlivebody-mac/; no swift run path. startBodyApp now logs + no-ops with FIXME(rewrite-2026-05-18). * docs(avlivebody-mac): contributor setup README Document prerequisites, mlpackage copy, signing xcconfig, and xcodegen/xcodebuild commands. Points at design spec and plan. * refactor(avlivebody): axis helper + cleanups - Extract arkitToRealityKit helper, dedupe 3 call sites. - Add onDisappear consumer.stop() to terminate USB read loop. - Replace @State with let for SceneController (stable class id). - Add NSLog diagnostics in VideoQuad+MeshEntity silent guards. * fix(avlivebody): ad-hoc signing for local dev Apple Development cert + Automatic signing makes Xcode demand a Mac Development cert that no one has. Switch to manual ad-hoc (CODE_SIGN_IDENTITY = -) so any contributor can build. Drop hardened runtime; re-enable for distribution builds. * feat(arbody): keep iphone awake while streaming iOS auto-lock tears down the USBServer TCP listener within seconds, breaking AVLiveBody Mac-side connect. Disable the idle timer for the lifetime of ContentView, restore on exit. * docs: add AGENTS.md skeleton
AV-Live
Live coding audio-visual performance system built around a SuperCollider sound engine, an openFrameworks oscilloscope visualizer driven by a real Hantek 6022BL USB scope, a macOS menubar launcher, and a Metal-native pose / body-mesh visualizer that listens to the same audio bus.
15 scripted demoparties · ~47 fullscreen visuals (26 3D parametric meshes + 18 procedural shaders + 3 dedicated C++ scenes) · 5 OS pixel-art shaders · 14 retro OS logos · 33 GLSL shader pairs (~65 files) · 1099 SynthDefs across 368 tracks (23 albums × 16) · 20 real-world data feeds · 7 pose-estimation backends · SMPL-X body mesh (10 475 vertices via Multi-HMR + RealityKit) · 3 launch modes (Full / Data-only / Body Mesh) — all reactive to the audio physically passing through the scope probes.
Top-level architecture
ASCII (always renders):
AV-LIVE
┌──────────────────────────────────────────┐
│ AVLiveLauncher.app (menubar) │
└────┬────────────┬─────────────┬──────┬───┘
│ │ │ │
┌───────▼──────┐ ┌───▼────┐ ┌──────▼───┐ ┌▼────────────┐
│ sclang + │ │ node │ │ oscope-of│ │ data_only_viz│
│ scsynth │ │ web ui │ │ (oF C++) │ │ (Metal py) │
│ sound_algo/ │ │ :3000 │ │ Hantek │ │ webcam pose │
│ 1099 SynthDef│ │ │ │ FFT 2048 │ │ 7 backends │
│ 368 tracks │ │ │ │ ~47 bg │ │ NLF / SMPL │
└───────┬──────┘ └───┬────┘ └──────┬───┘ └───┬─────────┘
:57121 OSC :3000 HTTP :57123 OSC :57123 OSC out
│ │ ▲ │
└────────────┴─────────────┴──────────────┘
▲
data_feeds/ (11 sources OSC)
Mermaid (rendered on GitHub):
flowchart TB
L["AVLiveLauncher.app (menubar SwiftUI)"]
SC["sound_algo/<br/>sclang + scsynth<br/>1099 SynthDefs · 368 tracks"]
W["sound_algo/web/<br/>node :3000"]
OF["oscope-of/<br/>openFrameworks C++<br/>Hantek 6022BL · FFT 2048<br/>~47 backgrounds · 33 shader pairs"]
DOV["data_only_viz/<br/>Metal natif (pyobjc)<br/>7 pose backends · NLF/SMPL mesh"]
DF["data_feeds/<br/>11 real-world sources"]
WR["web_realart/<br/>WebGPU + Web Audio + Hydra"]
L -->|spawn| SC
L -->|spawn| W
L -->|spawn| OF
L -->|spawn| DOV
SC -->|OSC :57123| OF
SC -->|OSC :57123| DOV
DOV -->|OSC pose →| SC
DF -->|OSC :57121| SC
DF -->|OSC :57123| OF
DF -->|OSC :57124| WR
W <-->|WebSocket| SC
Features
🎵 Sound engine — sound_algo/
- 1099 SynthDefs auto-loaded (acid_v1..v19, bass808_v1..v19, lead_v1..v19, kicks, drums, world, fx, master) — 1059 synth + 40 fx confirmed
- 23 albums × 16 tracks = 368 tracks ready to play (album letters A-W)
- Subtle humanize : timing ±3.5 ms, velocity ±10 %, micro-detune ±3 cents per note
- Synth rotation : cycles through 19 versions of each SynthDef every 8 notes
- Auto layering : 3 voice layers parallel to melody/harmony/acid
- 15 album signatures with per-album palette, FX drives, layer config
- OSC bridge to oscope-of and data_only_viz on
:57123(kick/snare/melody/lead/bass/bpm/album) - Web UI on
:3000with live coding control surface (Express + WebSocket bridge) - 18 example scripts in
sound_algo/examples/00..17_*.scd
📺 Visualizer — oscope-of/
- Hantek 6022BL real USB oscilloscope capture (1–48 MS/s, 2 channels, 8-bit) via libusb bulk transfers
- AudioAnalyzer : downsamples Hantek to 48 kHz, FFT 2048 (~23 Hz/bin), bands bass / lowMid / mid / treble / kick / snare
- ~47 fullscreen backgrounds (enum
BgKindinsrc/ofApp.h:98-113, 53 enum values) :- 26 ModelVis 3D parametric meshes : Möbius, Klein, trefoil, twisted torus, Lucy, helix, catenoid, Boys, lemniscate, Penrose, sphere, icosahedron, dodecahedron, torus, supershape, Lorenz attractor, Hopf, Enneper, Hopf link, gear, cone, pyramid, rose3D, geosphere, DNA helix, hyperboloid
- 18 ShaderVis procedural : metaballs, voronoi, twister bars, plasma FBM, rotozoom, truchet kaleido, SDF tunnel raymarched, KIFS fractal, fire, perspective grid, tunnel cubes, caustics, vortex, octahedron chrome, Boing ball, Mode7, plasma C64, dot tunnel
- 3 dedicated C++ scenes : TunnelVis, VectorCubesVis, SphereWaveVis
- + starfield (DemoFx) and + live webcam (WebcamVis)
- 5 OS pixel-art shaders : Workbench (Amiga), Mac OS Classic, Atari Fuji, Ocean Loader, Win95
- 14 OS logo PNGs with demoscene FX (mirror reflection, ghost trail, RGB chroma, twister wobble, scanlines, glitch tear) : Win 1.0/3.11/95, Lotus 1-2-3, MS-DOS, Apple Rainbow, Amiga WB, NeXTSTEP, BeOS, OS/2 Warp, IRIX, ZX Spectrum, C64, Atari
- 10 scroller styles : Classic, Wavy3D, Rainbow, Mirror, Glitch, Neon, Cascade, Chrome, Bouncy, Squashy
- Demoscene FX : sine scroller from
greetings.txt, copper bars, logo bobs, starfield with motion blur - 33 GLSL shader pairs (~65 files) : 14 backgrounds + 5 OS pixel-art + 9 post-FX/transitions + 5 mesh/3D + shared utilities (
postfx.vert,transition.vert), all GLSL 150 GL 3.2 core - Post-FX shader chain : ACES tone-map, bloom, chromatic aberration, hue rotation, scanlines, vignette, grain, pixelate, kaleido, feedback FBO, glitch displacement
🤸 Pose / body mesh — data_only_viz/ (new)
Metal-native fullscreen visualizer (pyobjc, MTKView, ~60 fps) driven by webcam pose detection. Listens to the same OSC bus as oscope-of (:57123) so audio sync and data feeds drive its shaders identically, and ships pose features back to sound_algo on :57121 to close the loop (the dancer steers the synths).
-
7 pose backends (drop-in via
pyproject.tomlextras) :Backend Module Model Status MediaPipe Holistic holistic.py33 body + 478 face + 42 hand kp stable YOLOv8-pose pose.pyUltralytics, 17 COCO kp stable (MPS) Apple Vision apple_vision_pose.pyVNDetectHumanBodyPoseRequest (ANE) macOS only Core ML pose coreml_pose.pyYOLO11n-pose CoreML, AVCapture zero-copy stable DETRPose detrpose.pyDETR transformer, COCO 17 kp, multi-person manual clone + ckpt NLF nlf_worker.pySMPL body mesh, 6890 vertices, TorchScript CUDA-only (CPU/MPS bricked) MediaPipe multi multi.pyPose/Face/Hand ×4 personnes stable -
Renderer : Metal pipelines compiled at runtime (
shaders/*.metal),bg_pipeline(full-screen FBM) +skel_pipeline(skeleton lines). SMPL face topology shipped as binary (mesh_topology.py) for RealityKit-compatible mesh rendering. -
Tracker : One Euro Filter on keypoints + IoU multi-person association (
scipy.linear_sum_assignment, ByteTrack-like). -
OSC out → sclang
:57121:/pose/count,/pose/center,/pose/wrist,/pose/head,/pose/sho_span,/pose/limb_span. -
Thread-safe state :
state.pyexposesState.lock(); dataclassesPoseKp,NLFPerson(vertices_3d, joints_3d), and a multi-person container.
🎬 Demoparties — 15 narrative demos
Each demoparty is a multi-act scripted show with unique scroller text, background sequence, scroller style, and SuperCollider album track :
| Key | Demo | Acts | Album |
|---|---|---|---|
1/& |
AMIGA TRIBUTE | 6 | M chiptune_madness |
2/é |
C64 LOWLIFE | 5 | N phonk |
3/" |
ACID JOURNEY | 8 | A acid_journey |
4/' |
TUNNEL VISION | 5 | P industrial |
5/( |
FREQUENCIES | 7 | I liquid_dnb |
6/§ |
GLITCH WORLD | 6 | V glitch_idm |
7/è |
AMBIENT VOID | 7 | J ambient_cinematic |
8/! |
RAVE | 7 | T hardcore_gabber |
9/ç |
MEMORY LANE | 8 | U vocal_trance |
0/à |
GREETINGS | 18 | W dub_techno |
F6 |
FRACTAL DREAMS | 7 | O asian_cinematic |
F7 |
INFERNO | 6 | E vietnam_hard |
F8 |
OUTRUN | 6 | R synthwave |
F9 |
CUBE STORM | 8 | Q neurofunk_dnb |
F10 |
GRAND FINAL | 8 | L future_garage |
Each act lasts 12–55 s, transitions between acts trigger flash + glitch postfx + audio swap. Demos loop on their first act after the last.
🎛 Live FX control (Mac AZERTY FR friendly)
a z e r t y u i o p— 10 background scenes (BoingBall, Tunnel, Metaballs, Voronoi, Twister, KIFS, Fire, Mode7, Octahedron, PlasmaC64)s d f g h j k l m+w x c v b n , ;— 17 FX parameters (speed, kick, roll, pan, curve, tile X/Z, dir lerp, bloom, chroma, kaleido, scanlines, pixelate, hue, grain, vignette, saturation)↑ ↓— adjust selected parameter (× 1.20 / × 0.83 multipliers, or ± 0.05 absolute):— reset all FX\or*— toggle beat-sync (queue demo trigger to next kick)q/Esc— exit narrative, default to SphereWave 3D live backgroundEspace— manual glitch pulseF1–F5— fullscreen / GUI / postFx / autoglitch / reload shaders
🌐 Real-world data feeds — data_feeds/ + web_realart/ + data_only_viz/web/
-
20 feed modules in
data_feeds/feeds/ingested bybridge.pyand broadcast as OSC/data/<source>/<sub>to SC (:57121), oF (:57123) and the web bridge (:57124) :- Geophysique : USGS quakes · Smithsonian GVP volcanoes · Blitzortung lightning
- Meteo / Air : Open-Meteo (temp/wind/rain) · OpenAQ (PM2.5/PM10/NO2/O3)
- Espace : NOAA SWPC (solar wind, Bz IMF, Kp, X-ray) · ISS position (wheretheiss.at) · GCN astrophysics
- Mobilite : OpenSky ADS-B · LiveATC listeners
- Energie / reseau : Mainsfrequenz.de · RTE eCO2mix · NOAA tides + moon phase
- Social / numerique : Bluesky firehose · Reddit /r/all + HackerNews top · Wikipedia EventStream · GDELT 2.0 events · GitHub · Bitcoin mempool
- Pose / webcam : YOLOv8-pose
-
SC presets
sound_algo/examples/16_data_feeds.scdand17_data_feeds_more.scdmap each source to synthesis : Schumann cavity drone (foudre), aurora additive pad (Bz/wind/Kp), Netzfrequenz pulse kick, RTE 8-op carbon FM, OpenSky granular swarm -
Web standalone
web_realart/ports the visualizers and synths to the browser forreal.art.saillant.cc:- WebGPU + three.js TSL (r171) globe with quake/strike/flight particles (auto-fallback WebGL2)
- Web Audio ports of 5 SC SynthDef presets (cavity, mix, geo, aurora, pulse)
- Hydra with 7 data-driven patches (aurora, quake, lightning, flightmap, gridpulse, solarwind, bskyrain)
- All three layers share
feeds_client.js(window.feeds) over one WebSocket
-
Web data-only
data_only_viz/web/(port:3211, Express + WebSocket) — bidirectional OSC bridge for the Data-only mode :/dashboard.html— live cards + SVG sparklines for all 19 active feeds (USGS, GDELT, Wiki, ISS, tides + moon, ATC, ...) with severity classes (alert / warn / green)/map.html— Leaflet dark fullscreen with markers ephemeres for geocoded feeds (quakes, lightning, planes, volcanoes, GDELT events) + persistent ISS marker/control.html— 3-pane control surface : 7 synth/mix sliders (master, cutoff, reso, reverb, delay, tempo), 10 audio scene buttons (/scene/play), 9 visual mode buttons routed to oF (/control/vizMode→:57123), XY pad (/xy/{x,y})- SC retour :
sound_algo/control/web_bridge.scdlistens on:57121for/control/*/scene/*/xy/*and pushes/sync/bpm|beat|rms|voicesto web on:57125at 4 Hz
📡 Audio reactivity pipeline
ASCII :
Hantek probes → bulk USB → ScopeRing CH1/CH2 (1-48 MS/s, 8-bit)
↓
AudioAnalyzer.update()
↓
Downsample box-filter → 48 kHz mono
↓
Hann window + FFT 2048 (23 Hz/bin)
↓
bands : bass(20-200) / lowMid(200-800) /
mid(800-3200) / treble(3200-16000) /
kick (transient bass) / snare (transient mid+treble)
↓
drives every visualizer parameter
Mermaid :
flowchart LR
H["Hantek 6022BL<br/>2× probes"] -->|bulk USB| R["ScopeRing<br/>CH1/CH2 1–48 MS/s 8-bit"]
R --> A["AudioAnalyzer.update()"]
A --> D["Downsample box-filter<br/>→ 48 kHz mono"]
D --> F["Hann + FFT 2048<br/>~23 Hz/bin"]
F --> B["Bands :<br/>bass · lowMid · mid · treble<br/>+ kick & snare transients"]
B --> V["Visualizer params<br/>(backgrounds, post-FX, scroller)"]
🤸 Pose pipeline — data_only_viz (new)
flowchart LR
CAM["Webcam<br/>AVCaptureSession"] --> BE{"Backend<br/>(pyproject extras)"}
BE -->|holistic| MP["MediaPipe Holistic"]
BE -->|pose| YO["YOLOv8-pose"]
BE -->|coreml_pose| CM["CoreML ANE"]
BE -->|apple_vision_pose| AV["Apple Vision"]
BE -->|detrpose| DT["DETRPose"]
BE -->|nlf| NLF["NLF SMPL 6890v<br/>CUDA only"]
MP & YO & CM & AV & DT & NLF --> KP["Keypoints / Mesh"]
KP --> EF["One Euro Filter"]
EF --> TR["Tracker IoU<br/>linear_sum_assignment"]
TR --> ST["State (lock)<br/>PoseKp · NLFPerson"]
ST --> MET["Metal renderer<br/>bg + skel pipelines"]
ST --> OSC["OSC /pose/* →<br/>sclang :57121"]
SCIN["sclang OSC :57123<br/>/sync /data"] --> ST
Quick start
macOS install
# 1. Clone
git clone https://github.com/electron-rare/AV-Live.git
cd AV-Live
# 2. Dependencies
brew install --cask supercollider
brew install libusb node uv
# openFrameworks 0.12 : https://openframeworks.cc/download/ (extract to ~/of)
# 3. Build the launcher (universal binary arm64+x86_64)
cd launcher && bash build.sh
# 4. Build oscope-of (one-time symlink for oF make)
ln -s "$PWD/../oscope-of" ~/of/apps/myApps/oscope-of
cd ~/of/apps/myApps/oscope-of
make OF_ROOT=$HOME/of -j4 Release
# 5. (optional) data_only_viz Metal pose viz
cd ../../../data_only_viz
uv sync # base (Metal + OSC + tracker)
uv sync --extra pose # + MediaPipe / YOLOv8 / Ultralytics
# uv sync --extra nlf # + NLF SMPL body mesh (CUDA only)
uv run python -m data_only_viz.main
# 6. Run the whole stack
open AV-Live/launcher/build/AVLiveLauncher.app
The launcher autostarts sclang + the relevant visualizers + the web bridges depending on the 3 launch modes offered by the picker at startup :
- Full AV-Live — sclang + oscope-of + sound_algo web UI (
:3000) + data_feeds (full profile) - Data-only — sclang +
data_only_vizMetal viz + data_feeds (data-only profile) + sound_algo web UI + data-only dashboard (:3211with/dashboard.html,/map.html,/control.html) - Body Mesh — sclang +
data_only_viz(headless, Multi-HMR worker only) +AVLiveBodySwift app (RealityKit, SMPL-X mesh + webcam overlay + liveRenderSettingspanelS) + data_feeds + dashboard data-only
Open the menubar icon for per-process Start/Stop, logs, and the "AV-Live-Body" launch button available from any mode.
Without Hantek hardware
oscope-of works without the scope plugged in. It falls back to synthesized signals from the OSC /sync/* metadata sent by sound_algo. You lose the audio-derived FFT but the demoscene visuals + narrative demos all still play. data_only_viz runs identically since it only needs the OSC bus + a webcam.
Demoparty mode
Press a digit (1–9, 0) or F-key (F6–F10) to launch a 1–3 minute scripted demoparty. Each one is a journey through audio + visual transitions, with greetings to the demoscene legends along the way (Fairlight, Razor 1911, ASD, Conspiracy, Farbrausch, TBL, Mercury, TPOLM, Loonies, Hokuto Force, IQ, Knighty, Smash, Gargaj…).
The 0 GREETINGS demo cycles through 18 acts including Boing Ball, plasma C64, dot tunnel, twister bars, copper bars, fractal KIFS, Möbius topology, then a parade of OS logos (Win 1.0, Lotus 1-2-3, Atari, Apple, OS/2 Warp, IRIX, ZX Spectrum), ending on a roll call of demoscene groups.
Architecture details
| Component | Path | Tech |
|---|---|---|
| Sound engine | sound_algo/ |
SuperCollider 3.14+, ~12k LOC .scd |
| Sound web UI | sound_algo/web/ |
Node Express + WebSocket bridge OSC↔HTTP |
| Visualizer | oscope-of/ |
openFrameworks 0.12, GLSL 150 GL 3.2 core, ~6k LOC C++ |
| Pose / body mesh | data_only_viz/ |
Python 3.11+ / uv, Metal natif via pyobjc, 7 pose backends |
| Launcher | launcher/ |
SwiftUI universal app, sentinel-based restart, ProcessManager @MainActor |
| Real-world feeds | data_feeds/ |
Python uv, 20 async feed modules, bridge.py OSC fan-out (3 targets) |
| Data-only web | data_only_viz/web/ |
Node Express :3211 + WS bidir bridge OSC, dashboard / map / control |
| Body mesh app | launcher/AV-Live-Body/ |
Swift 6 + RealityKit, SMPL-X 10 475 verts, LowLevelMesh + wireframe |
| Multi-HMR worker | data_only_viz/multi_hmr_worker.py |
PyTorch MPS, AVCaptureSession native, TCP sender :57130 |
| Web standalone | web_realart/ |
Node Express + ws, WebGPU three.js r171 + Web Audio + Hydra |
| Audio FFT | oscope-of/src/AudioAnalyzer.{h,cpp} |
downsample + Hann + FFT 2048 |
| 3D models | oscope-of/bin/data/models/ |
27 PLY parametric meshes |
| Sprites | oscope-of/bin/data/sprites/ |
14 PNG OS logos + 5 SVG sources |
| Shaders | oscope-of/bin/data/shaders/ |
33 GLSL pairs (~65 files) frag+vert |
| Metal shaders | data_only_viz/shaders/ |
.metal, runtime-compiled |
OSC ports : scsynth :57110 · sclang :57121 · node web :57122 · oscope-of :57123 (source: oscope-of/bin/data/settings.json) · web_realart :57124 · http :3000.
License
GPL-3.0-or-later. See LICENSE.
Credits & homages
Built by L'Électron Rare (Clément Saillant). Greetings to all the live coders, the demoscene legends, the openFrameworks community, the SuperCollider community, the Saillans festival 2026 artists, and everyone keeping the phosphor alive.
The GREETINGS scroller in oscope-of/bin/data/sprites/greetings.txt is the soul of the project — edit it freely and make it yours.
*** Press 1-0 + F6-F10 to start a demoparty ***
*** Keep the scene alive — 1985 to ∞ ***