Commit Graph

15 Commits

Author SHA1 Message Date
joren
cbc5083490 feat: real-time dB level meters per node
Backend:
- One pw_stream (INPUT, F32) per audio node; for sinks use
  stream.capture.sink=true to read the monitor port
- RT process callback computes instantaneous linear peak with no
  allocations; stored in std::atomic<float>
- Meter streams are filtered from the registry so they never appear
  in the graph or trigger recursive meter creation
- Meter streams are created on first node-ready event, destroyed on
  node removal, and cleaned up on engine close
- GET /api/peaks → {node_id: linear_peak} (polled by frontend)

Frontend:
- peaks store polled at 100 ms via setInterval; starts/stops with
  initGraph/destroyGraph
- Each node card grows 8 px and shows a 3 px meter bar at the bottom
  (green below -12 dB, yellow -12 to -3 dB, red above -3 dB)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 19:48:02 +02:00
joren
0d3cfb5f86 fix: broadcast external volume/mute changes + fix browser stream volume
Issue 1 - Volume/mute changes from external apps (browser YT player, pulsemixer)
not reflected in the frontend:
- on_node_param and on_node_info updated node state but never called notifyChanged(),
  so the SSE broadcast was never triggered for external changes.
- Add engine_ref back-pointer to Object (set in create_proxy_for_object).
- Call notifyChanged() at the end of on_node_info and after updating Props
  in on_node_param, so any external volume/mute change immediately broadcasts.

Issue 2 - Volume slider has no audible effect on browser/app streams:
- setNodeVolume only set SPA_PROP_volume (single float). Browser streams
  (Chromium, Firefox) use SPA_PROP_channelVolumes (per-channel float array)
  and ignore the single-float property.
- Now set both SPA_PROP_volume AND SPA_PROP_channelVolumes (using the node's
  known channel count, defaulting to stereo if unknown). ALSA hardware nodes
  respond to SPA_PROP_volume; app streams respond to SPA_PROP_channelVolumes.

Note: wires auto-reconnecting is WirePlumber session policy (by design) —
WirePlumber re-links any stream that loses its connection to the default sink.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 12:30:56 +02:00
joren
b3c81623f1 fix: mute race condition + profile loading + add Update button
Bug 1 - Mute unmute not sticking (G560 and others):
- Root cause: on_node_info was reading volume/mute from info->props which
  contains static initial values only — NOT updated at runtime. When any
  node info event fired, it overwrote the correct runtime state with stale
  initial data, causing the unmute to revert on the next graph event.
- Fix: Subscribe nodes to SPA_PARAM_Props in addition to SPA_PARAM_Format.
  Handle SPA_PARAM_Props in on_node_param to track volume (both SPA_PROP_volume
  and SPA_PROP_channelVolumes averaged) and mute from the authoritative live
  parameter stream. Remove stale volume/mute reads from on_node_info.
- Also fix mute detection in /api/mute: check "mute":true precisely instead
  of searching for bare "true" anywhere in the body.

Bug 2 - Loading profiles does not work:
- loadProfile was only applying connections when already in "activated" mode.
  Load now always applies the profile connections immediately.

Bug 3 - No option to update an existing profile:
- Add "Update" button in profile list that overwrites the profile with current
  connections (calls saveProfile with the existing name).
- Clear the profile name input after "Save Current" succeeds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 12:20:22 +02:00
joren
f76583a33e feat: buffer/latency control + non-selectable node text
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
2026-03-30 01:10:46 +02:00
joren
839eb5156a fix: read real sample rate from SPA_PARAM_Format (like pw-top)
- 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
2026-03-30 01:00:25 +02:00
joren
535ee058f2 fix: property inspector - always show channels/sample rate, show 'default' if unknown
- 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
2026-03-30 00:40:09 +02:00
joren
4cc6f554af fix: property inspector - read sample rate from node.latency, not node.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'
2026-03-30 00:36:40 +02:00
joren
8c728dcd47 feat: node property inspector
- Right-click node -> Properties shows details dialog
- Shows: ID, name, class, volume, ports, sample rate, channels,
  format, quantum, rate, device, bus, API, priority
- Backend parses extra PipeWire node props (clock.rate, channels, etc.)
- Node type includes all new fields
- stop() already had the SSE cleanup fix
2026-03-30 00:33:41 +02:00
joren
444fc43c9c fix: toolbar buttons clickable, add node delete
Fixes:
- SVG mousedown handler now ignores clicks outside SVG (toolbar works)
- Right-click node -> Destroy Node to delete virtual devices
- POST /api/destroy-node {node_id} endpoint (uses pw-cli destroy)

Node context menu shows node name and destroy button.
2026-03-30 00:02:53 +02:00
joren
9dc685acda feat: pactl-based module loading, more device types
Backend:
- Switched null-sink and loopback to use pactl (works on all systems)
- Generic POST /api/load-module {module, args} for any pactl module
- Fixed SVG toolbar click issue (z-index layering)

Frontend:
- + Add Device dropdown now includes:
  - Null Sink (virtual audio output)
  - Loopback Device (paired input+output)
  - TCP Network Server (module-native-protocol-tcp)
  - TCP Tunnel Sink
  - TCP Tunnel Source
- Dropdown header and section separators
- Fixed canvas z-index so toolbar is clickable
2026-03-29 23:55:19 +02:00
joren
2879469d13 feat: create virtual devices (+ Add Device dropdown)
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.
2026-03-29 23:50:01 +02:00
joren
044c5e551c fix: volume slider and mute button race conditions
- 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
2026-03-29 23:29:40 +02:00
joren
65db5daa7c feat: built-in volume management
- 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
2026-03-29 23:21:39 +02:00
joren
77e2fdca14 feat: full feature set - filters, context menu, patchbay, position persistence
Backend:
- GET/PUT /api/positions - server-side node position persistence (~/.config/pwweb/)
- GET/PUT /api/patchbay - save/load connection snapshots
- POST /api/patchbay/apply - apply saved connections

Frontend (custom SVG canvas):
- Right-click wire to disconnect (context menu)
- Port type filter toolbar (audio/midi/video/other toggles)
- Save/Load patchbay buttons
- Node positions persisted to localStorage + server
- Node border color by mode (green=output, red=input)
- Type indicator in node header [audio] [midi]
- Selected wire highlight (white, thicker)
- Select wire + Delete to disconnect
- Drag output port to input port to connect
- Pan (drag bg) and zoom (scroll wheel)
- Filtered ports shown as dim dots when hidden
2026-03-29 22:45:10 +02:00
joren
f8c57fbdd3 Initial commit: pwweb - PipeWire WebUI
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)
2026-03-29 22:40:07 +02:00