V210.hpp: add fill_colorbars, fill_ire_ramp, fill_black, fill_white.
Shared fill_solid helper stamps the first line across all rows via
memcpy. Generic write_palette_line template handles both SMPTE and
IRE bar palettes. fill_frame kept as a backward-compat alias.
testpattern/main.cpp: read "pattern" from node config and dispatch
to the appropriate fill function (bars/ire/black/white).
build_graph: pass {"pattern","bars"} to testpattern node params.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NDIlib_audio_frame_v3_t carries a FourCC. Only FLTP (float32 planar)
is handled — skip and free any other format rather than misinterpreting
compressed or integer audio bytes as floats.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NDIlib_audio_frame_v3_t.p_data is uint8_t*, not float*. Calling
vector<float>::assign(uint8_t*, uint8_t*+N) converted each individual
byte (0-255) into a separate float instead of reinterpreting 4 bytes
as one float sample — producing complete noise on playback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Audio flows in MXL use mxlFlowWriterOpenSamples/CommitSamples, not
the grain API (mxlFlowWriterOpenGrain is for discrete video flows).
Using the grain API silently did nothing — hence "last published index: 0".
MXL audio is float32 planar: each channel occupies its own ring buffer
region accessed at base.fragments[*].pointer + c * slice.stride.
Two fragments handle ring buffer wraparound. No int32 conversion needed.
Also log continuous.channelCount/bufferLength instead of the discrete
sliceSizes field (wrong union member for audio).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MXL reads channel_count (plain integer) to determine grain buffer
geometry, defaulting to 1 if absent. The NMOS IS-04 channels array
is ignored by MXL. Without channel_count, stereo audio grains were
half the required size.
Also: MXL only accepts bit_depth 32 or 64 for audio flows.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MXL FlowParser requires urn:x-nmos:tag:grouphint/v1.0 in the tags
object of every flow definition. make_audio_flow_def was missing tags
entirely, causing FlowParser.cpp:192 "Invalid group hint tag".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- FlowDef.hpp: add make_audio_flow_def (NMOS IS-04 audio/Lnn flow)
- NDIReceiver: replace capture_v210 with capture() — single recv call
dispatches to FrameKind::{Video,Audio,None}; AudioInfo struct carries
sample_rate/channels/samples/channel_stride
- ndiin: optional audio writer when graph wires audio_flow_id; single
loop with 5ms NDI poll; audio grains written immediately on arrival;
video grains written when mxlGetCurrentIndex reaches video_index
- build_graph: rename ndiin video port flow_id → video_flow_id; add
audio_flow_id edge (no sink yet — wired to ndiin only)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On status_change, probe() the new format and compare against the
format used to create the MXL flow. If resolution or fps changed,
throw with a descriptive message so the caller exits cleanly.
The existing catch in NDIInNode::run() logs the reason and breaks
out of the loop, causing the process to exit. Studio-manager detects
the exit and logs it — operator can restart to pick up the new format.
Same-format status changes (e.g. metadata only) still return false
and repeat the last frame as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Share a single UUID across both edges so both readers connect to the
same MXL flow. MXL supports multiple readers per flow natively — each
reader has its own read pointer into the ring buffer.
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>
On Linux, spdlog and fmt are compiled as static libs then linked into
libmxl.so. Without -fPIC the linker rejects TPOFF32 relocations inside
a shared object. Setting PIC globally fixes this for all fetched deps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UUID_SYSTEM_GENERATOR=ON requires libuuid on Linux and CoreFoundation
on macOS. Turning it off uses stduuid's built-in mt19937 generator,
which needs no system packages and works identically on all platforms.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the Homebrew dependency so the build works on any platform
with only CMake 3.24+ and a C++20 compiler. fmt and spdlog are now
fetched and compiled from source alongside the other FetchContent deps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>