Time and threading
The Treefall SDK core is host-neutral: it provides deterministic transport and routing primitives without choosing a UI toolkit, scheduler, or application-thread policy. Device-backed hosting adds a separate audio-driver callback boundary. Treat those as two integration layers rather than assuming that a host-neutral render call is a hardware callback.
Make samples the canonical clock
Section titled “Make samples the canonical clock”Represent a position as an integer sample count. TimePoint stores an int64_t sample value; seconds, beats, and non-drop-frame timecode are derived views that require an explicit sample rate (and tempo for beats). TimeRange is half-open, [start, end), and stores its start plus canonical length. Empty ranges are valid. This gives clip boundaries, comparisons, and distances one exact representation instead of repeatedly converting through floating-point seconds.
Use the conversion direction deliberately:
- Construct from
fromSamples()when a source or render boundary is already sample-addressed. - Construct from seconds or beats only at an explicit control-side boundary; those constructors round to the nearest sample.
- Keep the resulting
TimePointorTimeRangefor subsequent comparisons and arithmetic. - Derive display values with the sample rate and tempo in effect for that view. A display value is not a replacement for the stored sample coordinate.
Transport positions follow the same rule: the samples member is authoritative, while seconds and beats are derived. This is the basis for sample-accurate starts, fades, loops, and routing changes.
Source: time_domain.h and transport_controller.h.
Assign work to the correct owner
Section titled “Assign work to the correct owner”A host-neutral integration normally has control/message work and one audio consumer:
- Control or message threads perform file I/O, metadata edits, source registration and preparation, driver communication, and allocations or locking when the API permits them.
- The audio thread calls
processAudio()from exactly one non-reentrant consumer. It reads prepared state, advances playheads, applies gain/fades, and publishes bounded events. It must not allocate, lock, block, perform file or network I/O, make system calls, or invoke host callbacks. - The callback/message pump calls
processCallbacks()from exactly one control thread. It translates the audio-to-control event queue into host callbacks away from the audio callback. Call it even when no callback object is installed, because it also republishes tempo for lock-free beat queries.
Control producers may submit transport commands concurrently; this does not turn the complete control call into a realtime or wall-clock wait-free operation. Registry-dependent preparation and retained-payload reclamation remain control-side work. The audio consumer only processes bounded, already-published commands.
Source: ARCHITECTURE.md and transport_controller.h.
Treat malformed audio blocks as no-touch inputs
Section titled “Treat malformed audio blocks as no-touch inputs”The render contract is fixed when the controller is created. getRenderConfig() reports the exact output-channel count and maximum block size. The host must provide exactly that many writable planar buffers and no more than the configured frame count.
Validate the shape before handing it to the renderer:
- The output-buffer array is present and each required plane is writable.
- The number of planes equals
getRenderConfig().outputChannels. - The frame count does not exceed
getRenderConfig().maxBlockFrames. - The call is made by the one designated audio consumer, not concurrently or reentrantly.
The realtime audit requires pointer-array, exact-channel, and bounded-frame validation before command draining or output writes. A malformed shape is a no-touch return: do not clear, partially fill, or otherwise modify the caller’s buffers. A valid zero-frame block is different; it may advance bounded control state. Keep the distinction in host-side tests and diagnostics so an invalid block cannot be mistaken for a valid silent block.
For device callbacks, input and output channel counts are independent. Read the counts from the AudioProcessBlock; never infer one direction from the other, retain a block pointer, or use the callback as a place to recover from an invalid route.
Source: REALTIME_AUDIT.md, transport_controller.h, and audio_driver.h.
Keep callback lifetimes explicit
Section titled “Keep callback lifetimes explicit”A device callback receives buffers valid only for the duration of that invocation. It must not retain pointers, allocate, lock, block, perform I/O, or call into an application callback that can re-enter host policy. Stop and shutdown require explicit callback-lifetime coordination; a fixed sleep is not a resource-safety boundary. Perform teardown only after the callback owner has stopped and the control side has completed its documented drain/lease sequence.
This page describes ownership and timing contracts, not a cross-application dispatcher. The consuming host remains responsible for choosing its message pump and for presenting errors to users.
Source: REALTIME_AUDIT.md and audio_driver.h.