studio-manager: call mxlGarbageCollectFlows before launching nodes to
clean up stale flow directories left by previous crashed runs.
shared/NodeBase.hpp: add mxl_status_str(mxlStatus) — converts error
codes to readable names (e.g. MXL_ERR_OUT_OF_RANGE_TOO_LATE). All
nodes now log these names instead of raw integers.
shared/FlowDef.hpp: use "audio/float32" as media_type for 32-bit audio
flows, matching the MXL SDK examples. audio/L{n} kept for other depths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
flow_id is now optional (config().contains). When absent: skip the
MXL reader, active-wait, and NDI video path entirely. Video buffers
and NDI video frames are heap-allocated only when has_video.
Loop pacing: video branch sleeps 1ms on TOO_EARLY as before; audio-only
path sleeps 1ms when no chunk was available (audio_advanced == false)
to avoid busy-spinning.
NDI sender name changed from the video flow UUID to node_id(), which
is stable and human-readable for both video and audio-only modes.
To use audio-only: wire only audio_flow_id in build_graph, omit flow_id.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sample_rate / fps_num gave 48000/30000 = 1 for 29.97 fps, producing
silence. Correct formula is sample_rate * fps_den / fps_num, matching
the calculation already used in testpattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ndiout: p216_frame.FourCC was zero-initialized — NDI needs it explicitly
set to NDIlib_FourCC_video_type_P216 to send the correct format.
All three nodes: audio_rate = {sample_rate, 1} was always declared even
when has_audio is false (sample_rate = 0 in ndiout). Scoped into the
has_audio block so the bad rate can never be passed to mxlGetCurrentIndex.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix: c * 1920 → c * samples_per_frame (broke non-25fps or non-48kHz).
Fix: audio channel loop now iterates `channels` not `audio_slices.count`.
Rename: mxl_stride → video_stride, no_samples → samples_per_frame,
ndi_frame_10bit/16bit → v210_frame/p216_frame, ndi_audio_frame → ndi_audio.
Scope `ast` locally to its use block.
Drop unused #include "V210.hpp" and #include "FlowDef.hpp".
ndi_audio struct zero-initialized then filled only when has_audio.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NDIContext struct handles NDIlib_initialize/send_create/send_destroy/destroy
so NDIlib_destroy() is guaranteed even if send_create fails (was leaked before)
- malloc/free for 10-bit and 16-bit frame buffers → std::vector<uint8_t>
- NDI frame structs point into vector data, no manual lifetime management
- static_cast for FourCC instead of C-style cast
- Tidy: ndi_frame_count replaces ndi_frame_counter, ++prefix form
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>