Haiku BControllable: Parameter State, Timed Changes, and Notifications
Implement Haiku BControllable nodes with clear parameter ownership, performance-time updates, synchronized web access, and bounded change notifications.
BControllable is the Media Kit contract for exposing and changing a node’s parameter values. A BParameterWeb describes the structure and types of those controls; BControllable supplies the runtime behavior that reads, changes, and announces the actual values. The two are related, but they solve different problems. A web that describes a gain slider does not make the node apply a gain change, and a value callback does not by itself provide a useful user-facing control description.
The class is a virtual BMediaNode base and has protected construction and destruction. It is intended to participate in a media node, not to be used as an unrelated settings object. Its public and protected methods define a control plane with timing, ownership, synchronization, and notification responsibilities. Those responsibilities matter when a control panel, another node, and a media callback can all observe the same parameter.
Construct and own the parameter web deliberately
The header explicitly says to call SetParameterWeb() from the node’s constructor. The current implementation installs the supplied pointer, assigns the web’s node identity, notifies listeners that the web changed, and deletes the previous web when it is replaced. Treat this as ownership transfer. Do not pass a stack object, reuse a web after transferring it, or delete the installed web independently.
BParameterWeb* web = controllable->Web();
if (web == NULL)
return B_ERROR;
if (!controllable->LockParameterWeb())
return B_ERROR;
// Inspect the current parameter graph while its owner is protected.
// Do not keep a child pointer past UnlockParameterWeb() unless the API
// and node lifecycle explicitly guarantee that it remains valid.
controllable->UnlockParameterWeb();
Use the lock only around access to the parameter-web structure. It is not a general lock for every value in the media node. Keep the critical section short, and do not call into UI code or perform disk and network I/O while holding it. In a production implementation, use a small scope guard so every successful lock is paired with exactly one unlock, including early returns.
Replacing the web is a schema change, not just a value update. Consumers may have cached parameter IDs or descriptions. Notify through the documented web-change mechanism by using SetParameterWeb() as intended, and make the node’s IDs stable where compatibility matters. If a control disappears, ensure clients can handle the updated web rather than sending a stale ID forever.
Implement the value contract
Subclasses implement GetParameterValue() and SetParameterValue(). The getter supplies the current value and the time of its last change; the caller provides a buffer and size. Treat the buffer size as an in/out capacity, verify the parameter ID and expected type, and return a clear status for unknown IDs or undersized output. Do not write beyond the supplied capacity or return success with uninitialized bytes.
The setter receives a parameter ID, a performance-time timestamp, a pointer, and a byte size. Validate all four before accepting the change. A simple control might validate a scalar’s exact size and range. A complex control may need to schedule an update for the requested performance time rather than mutating the live signal path immediately. The API gives the timestamp; the node must decide how to honor it in its own processing architecture.
status_t GetParameterValue(int32 id, bigtime_t* lastChange,
void* value, size_t* ioSize) override;
void SetParameterValue(int32 id, bigtime_t when,
const void* value, size_t size) override;
The declarations above are a subclass interface sketch, not a complete node. In the getter, validate the caller’s size before copying and then return the exact size written. In the setter, copy data that must outlive the call into node-owned storage or a bounded queue. Never retain the caller’s pointer. Define what happens if the timestamp is in the past, too far in the future, or arrives while the node is stopping.
Distinguish value changes from control-structure changes
BroadcastNewParameterValue() announces a value change at a performance time so interested clients can stay synchronized. The header warns against calling it too densely because messages can flood the system. Use it for meaningful externally visible changes, not every sample or internal calculation. If a value changes rapidly as part of signal processing, decide on an appropriate reporting cadence and do not let UI synchronization overload the media path.
BroadcastChangedParameter(id) has a different role: it announces that the actual control has changed, such as a selector’s available choices changing after a device is inserted. Do not use it as a substitute for a new value notification. Structural changes can force clients to rebuild a control representation; excessive structural notifications are more disruptive than regular value updates.
Record the source of each update when useful: user control, hardware state, automation, or restored configuration. This makes it possible to prevent feedback loops in which a node broadcasts a value, a panel echoes it back, and the node treats the echo as a new user change. The API does not define your application’s conflict policy for simultaneous writers.
Respect the parameter-data buffer boundary
The header documents a B_MEDIA_PARAMETERS buffer representation containing a node identity and a count, followed by timestamped parameter records. Each record carries a control ID, a value size, and the value bytes. If the node implements BBufferConsumer for this format, the header recommends applying received data with ApplyParameterData(). MakeParameterData() is the corresponding helper for creating control information.
This is a typed control stream, not an excuse to reinterpret arbitrary bytes as a C++ struct. Validate buffer length and record boundaries through the supported API, reject unknown or malformed controls, and avoid assuming every parameter has the same size. Keep control messages bounded and avoid dynamic allocation in a time-sensitive callback unless the target architecture explicitly permits it.
The alternative request methods in BControllable can fetch or set values without connecting a control information source or destination. That convenience does not remove timestamp, status, or lifecycle requirements. If a node is not running, has no valid time source, or is shutting down, define how requests are answered and ensure callbacks cannot outlive the node.
Avoid lock inversion and notification storms
Parameter-web access may overlap node lifecycle callbacks and media-server requests. Establish a documented lock order between the web, node state, and any processing locks. Never acquire a window lock while holding a media parameter lock if the UI can call back into the node; that pattern can deadlock. Prefer copying the small parameter description needed by the UI under the web lock, releasing it, then updating the interface asynchronously.
Do not broadcast a notification while holding locks that a receiver could need to query the same node. Check the exact callback and thread context for the method you implement. A robust path validates and copies input, updates or schedules the state, releases internal locks, and only then publishes an appropriate notification.
Test the full control lifecycle
Test a successful get and set for each type, unknown IDs, undersized buffers, invalid enum values, changes requested at future and past times, web replacement, hardware-driven control changes, and node teardown while a panel is open. Verify that the web is owned once, stale IDs fail safely, notifications do not recursively echo, and a rapid stream of changes remains bounded. Measure callback duration under a busy client rather than assuming the notification mechanism is free.
For parameter state restoration, apply saved values only after the node has published the matching web. Reject values whose schema version, ID, type, or size no longer matches. The control web is a public interface contract; silently mapping an old control ID to a different meaning can produce plausible but incorrect behavior.
Acceptance criteria
Accept a BControllable implementation when the parameter web has a single clear owner, value methods validate size and identity, timestamps have defined semantics, structural and value notifications are separated, and observers cannot deadlock teardown or overload processing. Confirm those properties with a real media-server client as well as unit tests.
BControllable supplies a runtime parameter interface, not automatic control logic. A production node still owns scheduling, conflict policy, bounds, synchronization, and the meaning of each parameter.
Related:
- Haiku BParameterWeb: Describing Typed Controls on Media Nodes
- Haiku BMediaRoster: Discovering Nodes and Building Media Graphs
Sources: