Buffer Control:
- GET /api/quantum - reads current graph quantum via pw-metadata
- POST /api/quantum {quantum:N} - sets quantum via pw-metadata
- Toolbar dropdown with presets: 32, 64, 128, 256, 512, 1024, 2048, 4096
- Loads current quantum on startup
Text Selection Fix:
- Added user-select: none to wrap and canvas CSS
- Node text no longer gets selected when dragging
- Subscribe to SPA_PARAM_Format on each node via pw_node_subscribe_params
- Parse audio format with spa_format_audio_raw_parse to get actual rate/channels
- Shows real negotiated rates (48000 Hz, 44100 Hz, etc.) instead of defaults
- Added spa/param/format-utils.h, audio/format-utils.h, audio/raw-types.h
- Removed duplicated/broken code from property parsing
- Always shows Channels and Sample Rate for audio nodes
- Shows 'default' if sample rate not available on node
- Latency shows ms calculation when sample rate known
- Added fallback: clock.rate, api.alsa.rate
- node.rate was a PipeWire internal flag (always 1), not sample rate
- Now reads from node.latency (format: '256/48000') for quantum + rate
- Fallback to clock.rate if latency not available
- Reuses rate field for ALSA period-size
- Shows 'Latency: 256 samples @ 48000 Hz' and 'Period Size: 256'
- TCP Tunnel Sink/Source now open a dialog asking for host:port
- TCP Network Server uses configurable port (shown in dropdown)
- Host defaults to 127.0.0.1, port defaults to 4713
- Config persists across clicks (netHost/netPort state)
Backend:
- POST /api/create-null-sink {name} - loads null-sink module
- POST /api/create-loopback {name} - loads loopback module
- POST /api/unload-module {module_id} - unloads a module
- Fixed double-proxy-destroy crash in GraphEngine
- Graceful failure when module not available (no crash)
Frontend:
- + Add Device button in toolbar with dropdown menu
- Null Sink option (creates virtual audio output)
- Loopback Device option (creates paired input+output)
- Dropdown closes on outside click
Note: null-sink requires libpipewire-module-null-sink to be installed.
Loopback works on all PipeWire installations.
Added transparent hitarea rect over the full slider area (14px tall).
Handle is pointer-events:none so clicks pass through to hitarea.
Clicking anywhere on the slider bar now jumps volume there.
- Mute button click no longer triggers node drag (priority check in onMouseDown)
- Volume slider clamped to 0-100% in both display and API
- Added draggable circle handle on slider end
- Volume drag state tracked globally, not per-element
- Backend: fixed spa_pod_builder API usage (push_object/pop with frame)
- Volume calculated from SVG coordinates properly via svgPoint conversion
- Volume slider on every node (green bar, draggable)
- Mute toggle button (M/m) on every node
- Backend: read volume/mute from PipeWire node props
- Backend: POST /api/volume {node_id, volume} to set volume
- Backend: POST /api/mute {node_id, mute} to toggle mute
- Graph JSON includes volume and mute fields per node
- Slider supports drag-to-adjust with mouse
C++ backend with SSE streaming (reuses qpwgraph PipeWire callbacks).
Svelte frontend with custom SVG canvas for port-level connections.
Features:
- Live PipeWire graph via SSE
- Drag output->input port to connect
- Double-click or select+Delete to disconnect
- Node positions saved to localStorage
- Pan (drag bg) and zoom (scroll)
- Port type coloring (audio=green, midi=red, video=blue)