Added tons of documentation headers to functions in source files.

This commit is contained in:
Johannes Findeisen 2026-10-27 01:58:27 +01:00
commit 4eb2080933
43 changed files with 3953 additions and 43 deletions

View file

@ -11,9 +11,20 @@ extern "C" {
typedef struct xtc_app xtc_app;
/* Creates an application context. On success, *out_app is caller-owned. */
/**
* @brief Allocates an application handle.
*
* @param out_app Receives the newly allocated app; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_app_create(xtc_app **out_app);
/* Destroys an application context; NULL is accepted. */
/**
* @brief Releases an application handle; accepts NULL.
*
* @param app Application handle.
*/
void xtc_app_destroy(xtc_app *app);
#ifdef __cplusplus

View file

@ -14,21 +14,78 @@ typedef struct xtc_recorder xtc_recorder;
* mono, 1..768000 Hz. Absolute canonical references, <=511 bytes.
* Adds a timeline source -> output route if no timeline source exists.
* Transactional; track must exist. Duration rounds up to a musical tick. */
/**
* @brief Imports a WAV reference as a clip and ensures a timeline output route exists.
*
* @param project Project model; borrowed for this call.
* @param track Stable track identifier.
* @param path NUL-terminated file or asset path.
* @param start_tick Start position in musical ticks.
* @param out_clip Receives the clip.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_import_audio(xtc_project *project, uint64_t track, const char *path, uint64_t start_tick,
uint64_t *out_clip);
/* Empty path makes a region silent; nonempty path must be absolute. Does not
* open the file. Missing references survive save/load for later relinking. */
/**
* @brief Sets an absolute audio asset reference, or clears it to make a region silent.
*
* @param project Project model; borrowed for this call.
* @param clip Stable clip identifier.
* @param path NUL-terminated file or asset path.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_clip_asset(xtc_project *project, uint64_t clip, const char *path);
/* Immutable/preloaded plan. At most 64 distinct assets, 16M mono frames/asset,
* 64M total frames, 256 MiB file and 4096 chunk input limits. Missing/invalid assets become silence with explicit
* issues. OOM/capacity fails creation. No file access during render. */
/**
* @brief Preloads audio assets and compiles an immutable arrangement snapshot for rendering.
*
* @param project Project model; borrowed for this call.
* @param rate Sample rate in hertz.
* @param out_plan Receives the newly allocated plan; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_audio_plan_create(const xtc_project *project, unsigned int rate, xtc_audio_plan **out_plan);
/**
* @brief Releases preloaded samples and arrangement state; accepts NULL.
*
* @param plan Preloaded arrangement plan.
*/
void xtc_audio_plan_destroy(xtc_audio_plan *plan);
/**
* @brief Returns the number of asset issues and optionally the first affected path.
*
* @param plan Preloaded arrangement plan.
* @param out_first_path Receives the first path.
* @return Number of unique asset issues; zero for a NULL plan.
*/
size_t xtc_audio_plan_issues(const xtc_audio_plan *plan, const char **out_first_path);
/* Single consumer; output replaced with overlap-summed mono at unity gain.
* Linear rate conversion, no time stretching. Output must have frames floats. */
/* Stereo outputs must be distinct; NULL right selects mono gain-only rendering. */
xtc_result xtc_audio_plan_render_stereo(const xtc_audio_plan *, uint64_t, float *left, float *right, size_t);
/**
* @brief Replaces output with the overlap-summed timeline mix, applying track gain and pan.
*
* @param plan Preloaded arrangement plan.
* @param position Absolute sample position at the start of the block.
* @param left Destination left-channel buffer.
* @param right Destination right-channel buffer; NULL selects mono rendering.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_audio_plan_render_stereo(const xtc_audio_plan * plan, uint64_t position, float *left, float *right, size_t frames);
/**
* @brief Renders the timeline to a mono output buffer using track gain without pan.
*
* @param plan Preloaded arrangement plan.
* @param position Absolute sample position at the start of the block.
* @param output Destination buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_audio_plan_render(const xtc_audio_plan *plan, uint64_t position, float *output, size_t frames);
/* Control-thread creation opens a same-directory temporary float32 WAV.
* Ring capacity 1..2097152 frames. One callback producer, one control consumer.
@ -37,11 +94,51 @@ xtc_result xtc_audio_plan_render(const xtc_audio_plan *plan, uint64_t position,
* drains, finalizes and atomically renames even on overflow (partial recovery).
* No source/recorder is freed until destroy, which also removes unfinished temp.
* Counts and sticky error can be observed while producing. */
/**
* @brief Allocates a bounded recording ring and opens a temporary mono float32 WAV file.
*
* @param path NUL-terminated file or asset path.
* @param rate Sample rate in hertz.
* @param capacity Recording ring capacity in frames, from 1 to 2097152.
* @param out_recorder Receives the newly allocated recorder; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_recorder_create(const char *path, unsigned int rate, size_t capacity, xtc_recorder **out_recorder);
/**
* @brief Enqueues an entire audio block without blocking; overflow sets a sticky error.
*
* @param recorder Recorder handle.
* @param input Borrowed input sample buffer.
* @param frames Number of audio frames.
*/
void xtc_recorder_push(xtc_recorder *recorder, const float *input, size_t frames);
/**
* @brief Writes queued samples to the temporary WAV file on the control thread.
*
* @param recorder Recorder handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_recorder_drain(xtc_recorder *recorder);
/**
* @brief Drains and finalizes the WAV file, then renames it after producer quiescence.
*
* @param recorder Recorder handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_recorder_finish(xtc_recorder *recorder);
/**
* @brief Copies the number of accepted frames and returns the sticky recording status.
*
* @param recorder Recorder handle.
* @param out_frames Receives the frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_recorder_status(const xtc_recorder *recorder, uint64_t *out_frames);
/**
* @brief Releases recorder storage and removes any unfinished temporary file; accepts NULL.
*
* @param recorder Recorder handle.
*/
void xtc_recorder_destroy(xtc_recorder *recorder);
#ifdef __cplusplus

View file

@ -44,46 +44,178 @@ typedef struct {
* Returns IO if server/client setup fails. On failure *out_backend is NULL,
* except if cleanup itself fails: a non-NULL handle must be closed/retried;
* only close is permitted on that partially initialized handle. */
/**
* @brief Opens a JACK client and registers its ports without starting the server or connecting routes.
*
* @param config Backend kind, client name, block capacity, and output channel configuration.
* @param out_backend Receives the newly allocated backend; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_open(const xtc_backend_config *config, xtc_backend **out_backend);
/* Stop/close establish callback quiescence before state can be destroyed.
* On failure the handle remains owned by caller; retry close. NULL close OK. */
/**
* @brief Quiesces callbacks and releases the backend; failed cleanup leaves the handle owned by the caller.
*
* @param backend Backend handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_close(xtc_backend *backend);
/**
* @brief Activates the backend and enables audio processing.
*
* @param backend Backend handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_start(xtc_backend *backend);
/**
* @brief Deactivates the backend and establishes callback quiescence.
*
* @param backend Backend handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_stop(xtc_backend *backend);
/* Stopped only; changes transport clock, not oscillator phase. */
/**
* @brief Changes the stopped backend's sample position and resets MIDI voices.
*
* @param backend Backend handle.
* @param sample Absolute sample position.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_seek(xtc_backend *backend, uint64_t sample);
/* Copies graph. While running, capacity means pending slot busy.
* While stopped, transactionally replaces all retained plans (counters/revisions
* restart); failed compilation leaves the previous engine intact. */
/**
* @brief Compiles and submits a graph while preserving the previous engine on failure.
*
* @param backend Backend handle.
* @param graph Graph model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph);
/* Stopped only. Preloads immutable audio and submits graph before committing.
* Asset issues are silent and reported in status/asset_issue. Other failures
* retain the previous plans. Arrangement edits are adopted on the next prepare. */
/**
* @brief Preloads project audio and MIDI and submits its graph while stopped.
*
* @param backend Backend handle.
* @param project Project model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_prepare_project(xtc_backend *backend, const xtc_project *project);
/**
* @brief Returns the first asset issue path, or an empty string when none is available.
*
* @param backend Backend handle.
* @return Borrowed path string, or an empty string if no issue is available.
*/
const char *xtc_backend_asset_issue(const xtc_backend *backend);
/* Borrowed recorder, set/detach only while stopped. Stop before finish/destroy. */
/* Control-thread panic is delivered at a block boundary via lock-free counter.
* Stops, seeks, xruns and event overflow also clear instrument voices. */
/**
* @brief Requests an instrument panic at the next audio block boundary.
*
* @param backend Backend handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_panic(xtc_backend *backend);
/**
* @brief Returns the borrowed name of the backend's MIDI input port.
*
* @param backend Backend handle.
* @param out_name Receives the name.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_midi_port_name(const xtc_backend *backend, const char **out_name);
/**
* @brief Attaches or detaches a borrowed recorder while the backend is stopped.
*
* @param backend Backend handle.
* @param recorder Recorder handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_set_recorder(xtc_backend *backend, xtc_recorder *recorder);
/**
* @brief Reclaims retired realtime plans on the control thread.
*
* @param backend Backend handle.
*/
void xtc_backend_collect(xtc_backend *backend);
/* Snapshot fields are individually coherent, not a transactional snapshot. */
/**
* @brief Copies individually coherent backend counters and transport state.
*
* @param backend Backend handle.
* @param out_status Receives the status.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_get_status(const xtc_backend *backend, xtc_backend_status *out_status);
/* Borrowed names valid until close. Direction is the client's own port. */
/* Output channel 0 is legacy output, channel 1 is output_right in stereo. */
xtc_result xtc_backend_output_name(const xtc_backend *, size_t channel, const char **out_name);
/**
* @brief Returns the borrowed name of an output channel port.
*
* @param backend Backend handle.
* @param channel Output channel index: 0 for primary, 1 for stereo right.
* @param out_name Receives the name.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_output_name(const xtc_backend * backend, size_t channel, const char **out_name);
/**
* @brief Returns the borrowed name of the mono input or primary output port.
*
* @param backend Backend handle.
* @param direction Local port direction.
* @param out_name Receives the name.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_port_name(const xtc_backend *backend, xtc_port_direction direction, const char **out_name);
/* Enumerates available mono audio ports (devices and software routes). Visitor
* receives a borrowed name valid only during call; return nonzero to stop.
* Control-thread only. JACK NULL list means no matching ports. */
/**
* @brief Inspects one available audio port during control-thread enumeration.
*
* @param name Borrowed port name, valid only during this callback.
* @param context Opaque pointer supplied to xtc_backend_visit_ports().
* @return Nonzero to stop enumeration, zero to continue.
*/
typedef int (*xtc_backend_port_visitor)(const char *name, void *context);
/**
* @brief Visits available audio ports matching the requested direction until the visitor stops.
*
* @param backend Backend handle.
* @param direction Local port direction.
* @param visitor Visitor called with each borrowed port name.
* @param context Opaque callback context.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_visit_ports(xtc_backend *backend, xtc_port_direction direction, xtc_backend_port_visitor visitor,
void *context);
/* Connect/disconnect this client's port with a remote opposite-direction port.
* own_direction INPUT receives remote output; OUTPUT sends to remote input.
* Explicit user choice only; no automatic physical routing. */
/**
* @brief Connects this client's port to a remote port with the opposite direction.
*
* @param backend Backend handle.
* @param own_direction Local port direction.
* @param remote_name Borrowed remote port name.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_connect(xtc_backend *backend, xtc_port_direction own_direction, const char *remote_name);
/**
* @brief Disconnects this client's port from a remote port.
*
* @param backend Backend handle.
* @param own_direction Local port direction.
* @param remote_name Borrowed remote port name.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_disconnect(xtc_backend *backend, xtc_port_direction own_direction, const char *remote_name);
#ifdef __cplusplus

View file

@ -81,35 +81,172 @@ typedef struct {
/* Read-only snapshots; indexes enumerate dense lists, distinct from stable IDs.
* No borrowed pointers into graph storage are exposed. */
/**
* @brief Copies the graph's node and connection counts.
*
* @param graph Graph model; borrowed for this call.
* @param out_nodes Receives the nodes.
* @param out_connections Receives the connections.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_counts(const xtc_graph *graph, size_t *out_nodes, size_t *out_connections);
/**
* @brief Copies node metadata selected by dense list index.
*
* @param graph Graph model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param out_node Receives the node.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_node(const xtc_graph *graph, size_t index, xtc_node_info *out_node);
/**
* @brief Copies connection metadata selected by dense list index.
*
* @param graph Graph model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param out_connection Receives the connection.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_connection(const xtc_graph *graph, size_t index, xtc_connection_info *out_connection);
/* Set required=1 for explicit output nodes only (none means silence).
* Default 0 retains terminal-node fallback for original offline callers. */
/**
* @brief Sets whether rendering requires explicit output nodes rather than terminal-node fallback.
*
* @param graph Graph model; borrowed for this call.
* @param required One to require explicit output nodes, zero to allow terminal-node fallback.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_require_output_nodes(xtc_graph *graph, int required);
/* Transactional command: on success caller owns a new graph; source unchanged.
* Failure sets *out_graph=NULL, leaves source and out_added_id unchanged.
* out_added_id is optional and written only for successful ADD_NODE. */
/**
* @brief Applies an edit to a new graph copy, leaving the source unchanged.
*
* @param source Graph model; borrowed for this call.
* @param edit Graph edit command.
* @param out_graph Receives the newly allocated graph; caller owns it on success.
* @param out_added_id Receives the added id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_apply_edit(const xtc_graph *source, const xtc_graph_edit *edit, xtc_graph **out_graph,
size_t *out_added_id);
/**
* @brief Allocates an empty graph with the requested node capacity.
*
* @param max_nodes Maximum number of graph nodes.
* @param out_graph Receives the newly allocated graph; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_create(size_t max_nodes, xtc_graph **out_graph);
/**
* @brief Releases graph storage; accepts NULL.
*
* @param graph Graph model; borrowed for this call.
*/
void xtc_graph_destroy(xtc_graph *graph);
/**
* @brief Adds a gain node with a finite linear gain value.
*
* @param graph Graph model; borrowed for this call.
* @param gain Linear gain, 0 to 4 for track controls.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_gain(xtc_graph *graph, float gain, size_t *out_node_id);
/**
* @brief Connects a source's output port 1 to a destination's input port 0.
*
* @param graph Graph model; borrowed for this call.
* @param source_node Stable source node identifier.
* @param destination_node Stable destination node identifier.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_connect(xtc_graph *graph, size_t source_node, size_t destination_node);
/* Port 0 is input; port 1 is output. Oscillators, audio sources and instruments have only output 1;
* output nodes have only input 0. All ports carry mono audio. */
/**
* @brief Copies the direction, signal type, and channel count of a node port.
*
* @param graph Graph model; borrowed for this call.
* @param node_id Stable node identifier.
* @param port_id Node port identifier.
* @param out_port Receives the port.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_port(const xtc_graph *graph, size_t node_id, size_t port_id, xtc_port_info *out_port);
/**
* @brief Adds a validated audio connection while preserving an acyclic graph.
*
* @param graph Graph model; borrowed for this call.
* @param source_node Stable source node identifier.
* @param source_port Source output port identifier.
* @param destination_node Stable destination node identifier.
* @param destination_port Destination input port identifier.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_connect_ports(xtc_graph *graph, size_t source_node, size_t source_port, size_t destination_node,
size_t destination_port);
/**
* @brief Adds a sine oscillator node with a positive frequency in hertz.
*
* @param graph Graph model; borrowed for this call.
* @param frequency Oscillator frequency in hertz.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_oscillator(xtc_graph *graph, float frequency, size_t *out_node_id);
/**
* @brief Adds a pass-through node that records the last block's absolute peak.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_meter(xtc_graph *graph, size_t *out_node_id);
/**
* @brief Adds a source node for the separately rendered MIDI instrument buffer.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_instrument(xtc_graph *graph, size_t *out_node_id);
/**
* @brief Adds a source node for the separately rendered timeline audio buffer.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_audio(xtc_graph *graph, size_t *out_node_id);
/**
* @brief Adds an explicit output node included in the final mix.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_output(xtc_graph *graph, size_t *out_node_id);
/* Compile copies graph state; caller owns the plan. Sample rate must be
* finite and positive, oscillator frequencies <= Nyquist. max_frames: 1..65536.
* Editing/destroying the graph never changes an existing plan. */
/**
* @brief Allocates an independent execution plan with preallocated processing buffers.
*
* @param graph Graph model; borrowed for this call.
* @param sample_rate Sample rate in hertz.
* @param max_frames Maximum frames per processing block.
* @param out_plan Receives the newly allocated plan; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_compile(const xtc_graph *graph, double sample_rate, size_t max_frames, xtc_plan **out_plan);
/**
* @brief Releases a compiled plan and its processing state; accepts NULL.
*
* @param plan Compiled graph plan and its single-owner runtime state.
*/
void xtc_plan_destroy(xtc_plan *plan);
/* Single processing owner, no concurrent calls (including meter reads/reset).
* No allocation, I/O, locking or graph validation. NULL input means silence.
@ -122,22 +259,96 @@ void xtc_plan_destroy(xtc_plan *plan);
/* Stereo graph with paired source buffers and a mono external input duplicated
* to both sides. Output buffers must be distinct and must not alias sources.
* Stereo oscillator phase advances once per frame. NULL right selects mono. */
xtc_result xtc_plan_process_stereo(xtc_plan *, const float *input, const float *audio_left, const float *audio_right,
/**
* @brief Processes a block with separate stereo timeline and instrument sources.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @param input Borrowed input sample buffer.
* @param audio_left Borrowed left timeline source buffer; NULL means silence.
* @param audio_right Borrowed right timeline source buffer; NULL means silence.
* @param midi_left Borrowed left instrument source buffer; NULL means silence.
* @param midi_right Borrowed right instrument source buffer; NULL means silence.
* @param left Destination left-channel buffer.
* @param right Destination right-channel buffer; NULL selects mono rendering.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_process_stereo(xtc_plan * plan, const float *input, const float *audio_left, const float *audio_right,
const float *midi_left, const float *midi_right, float *left, float *right,
size_t frames);
/**
* @brief Processes a mono block with separate timeline and instrument sources.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @param input Borrowed input sample buffer.
* @param timeline Borrowed timeline source buffer; NULL means silence.
* @param instrument Borrowed instrument source buffer; NULL means silence.
* @param output Destination buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_process_sources(xtc_plan *plan, const float *input, const float *timeline, const float *instrument,
float *output, size_t frames);
/* timeline is a separate mono input for XTC_NODE_AUDIO, NULL means silence. */
/**
* @brief Processes a mono block with timeline audio and no instrument source.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @param input Borrowed input sample buffer.
* @param timeline Borrowed timeline source buffer; NULL means silence.
* @param output Destination buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_process_audio(xtc_plan *plan, const float *input, const float *timeline, float *output,
size_t frames);
/**
* @brief Processes a mono block with external input and no timeline or instrument sources.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @param input Borrowed input sample buffer.
* @param output Destination buffer with room for the requested frames.
* @param frame_count Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_process(xtc_plan *plan, const float *input, float *output, size_t frame_count);
/* Resets oscillator phases and meters, without allocation. */
/**
* @brief Resets oscillator phases, meters, and native processor state without allocating.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_reset(xtc_plan *plan);
/* Last block absolute peak, valid for meter nodes only. */
/**
* @brief Copies the last block's absolute peak for a meter node.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @param node_id Stable node identifier.
* @param out_peak Receives the peak.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_meter_peak(const xtc_plan *plan, size_t node_id, float *out_peak);
/**
* @brief Checks graph validity by attempting a topological ordering.
*
* @param graph Graph model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_validate(const xtc_graph *graph);
/* Allocating compatibility helper: compiles at 48000 Hz and processes once.
* Requires non-NULL input/output and finite input_gain. Not for callbacks. */
/**
* @brief Compiles at 48000 Hz, applies input gain, renders once, and releases temporary state.
*
* @param graph Graph model; borrowed for this call.
* @param input Borrowed input sample buffer.
* @param output Destination buffer with room for the requested frames.
* @param frame_count Number of audio frames.
* @param input_gain Finite linear gain applied to external input before processing.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_process_offline(xtc_graph *graph, const float *input, float *output, size_t frame_count,
float input_gain);

View file

@ -32,36 +32,149 @@ typedef struct xtc_midi_plan xtc_midi_plan;
/* Control-thread model APIs, stable note IDs; deletion cascades from clips/tracks.
* MIDI clips cannot reference audio. Creation transactionally adds an instrument
* source -> output route if no instrument exists. Other clip edits stay shared. */
/**
* @brief Adds a MIDI clip and ensures an instrument output route exists.
*
* @param project Project model; borrowed for this call.
* @param track Stable track identifier.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param out_id Receives the id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_add_midi_clip(xtc_project *project, uint64_t track, uint64_t start, uint64_t length,
uint64_t *out_id);
/**
* @brief Copies the number of notes in the project.
*
* @param project Project model; borrowed for this call.
* @param out_count Receives the count.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_note_count(const xtc_project *project, size_t *out_count);
/**
* @brief Copies a note snapshot selected by dense list index.
*
* @param project Project model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param out_note Receives the note.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_note(const xtc_project *project, size_t index, xtc_note_info *out_note);
/**
* @brief Adds a validated MIDI note with a new stable identifier.
*
* @param project Project model; borrowed for this call.
* @param clip Stable clip identifier.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param pitch MIDI note number, 0 to 127.
* @param velocity MIDI note velocity, 1 to 127.
* @param channel MIDI channel, 0 to 15.
* @param out_id Receives the id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_add_note(xtc_project *project, uint64_t clip, uint64_t start, uint64_t length,
unsigned int pitch, unsigned int velocity, unsigned int channel, uint64_t *out_id);
/**
* @brief Replaces a note's source range, pitch, velocity, and channel after validation.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param pitch MIDI note number, 0 to 127.
* @param velocity MIDI note velocity, 1 to 127.
* @param channel MIDI channel, 0 to 15.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_edit_note(xtc_project *project, uint64_t id, uint64_t start, uint64_t length, unsigned int pitch,
unsigned int velocity, unsigned int channel);
/**
* @brief Removes a note selected by stable identifier.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_remove_note(xtc_project *project, uint64_t id);
/* Complete 3-byte note on/off and CC120/123 only. Velocity-zero on -> off.
* Unsupported messages return UNSUPPORTED; malformed supported messages INVALID.
* Output unchanged on error. No running-status/SysEx/pedal/program/bend support. */
/**
* @brief Decodes a complete note or all-notes-off MIDI message into a block event.
*
* @param bytes Borrowed MIDI message bytes.
* @param size Number of message bytes.
* @param offset Sample offset within the current block.
* @param out_event Receives the event.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_midi_decode(const unsigned char *bytes, size_t size, unsigned int offset, xtc_midi_event *out_event);
/* Control-thread compile; immutable ranges plus exclusively audio-owned runtime.
* Rejects >32 simultaneous scheduled notes. Separate 32 live voices steal oldest.
* Constant-tempo sine instrument, velocity *0.1, above-Nyquist notes silent. */
/**
* @brief Compiles scheduled notes and mixer state into a bounded sine-instrument plan.
*
* @param project Project model; borrowed for this call.
* @param sample_rate Sample rate in hertz.
* @param out_plan Receives the newly allocated plan; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_midi_plan_create(const xtc_project *project, unsigned int sample_rate, xtc_midi_plan **out_plan);
/**
* @brief Releases scheduled notes and instrument state; accepts NULL.
*
* @param plan Instrument plan and its single-owner runtime state.
*/
void xtc_midi_plan_destroy(xtc_midi_plan *plan);
/* Audio owner or stopped only. Reset clears live notes and panic suppression.
* Panic silences live notes and scheduled notes already started at position;
* later scheduled notes can start. MIDI CC120/123 affects its own channel. */
/**
* @brief Clears live voices, scheduling continuity, and panic suppression.
*
* @param plan Instrument plan and its single-owner runtime state.
*/
void xtc_midi_plan_reset(xtc_midi_plan *plan);
/**
* @brief Silences live voices and suppresses scheduled notes already started at the given position.
*
* @param plan Instrument plan and its single-owner runtime state.
* @param position Absolute sample position at the start of the block.
*/
void xtc_midi_plan_panic(xtc_midi_plan *plan, uint64_t position);
/* No allocation, I/O, locks or GUI. Events sorted by offset, equal offsets retain
* caller order, count<=256, frames<=65536. Invalid input leaves state/output alone.
* Renders [position,position+frames); discontinuity clears live voices, chases
* scheduled notes at their correct phase. Note-off is immediate (no envelope). */
/* Distinct stereo outputs; NULL right retains mono (gain only). */
xtc_result xtc_midi_plan_render_stereo(xtc_midi_plan *, uint64_t, const xtc_midi_event *, size_t, float *left,
float *right, size_t);
/**
* @brief Renders scheduled and live MIDI with sample-offset events and track gain and pan.
*
* @param p Instrument plan and its single-owner runtime state.
* @param position Absolute sample position at the start of the block.
* @param events Borrowed MIDI events sorted by sample offset.
* @param count Number of MIDI events.
* @param left Destination left-channel buffer.
* @param right Destination right-channel buffer; NULL selects mono rendering.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_midi_plan_render_stereo(xtc_midi_plan * p, uint64_t position, const xtc_midi_event * events, size_t count, float *left,
float *right, size_t frames);
/**
* @brief Renders scheduled and live MIDI to mono without stereo pan attenuation.
*
* @param plan Instrument plan and its single-owner runtime state.
* @param position Absolute sample position at the start of the block.
* @param events Borrowed MIDI events sorted by sample offset.
* @param count Number of MIDI events.
* @param output Destination buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_midi_plan_render(xtc_midi_plan *plan, uint64_t position, const xtc_midi_event *events, size_t count,
float *output, size_t frames);

View file

@ -25,11 +25,53 @@ typedef struct {
* Linear pan: center unity on both outputs; hard pan attenuates opposite side.
* Automation replaces the corresponding static value, linear between points,
* holds first/last outside the lane. Duplicate track/parameter/tick replaces. */
xtc_result xtc_project_set_mix(xtc_project *, uint64_t track, float gain, float pan, int mute, int solo);
xtc_result xtc_project_set_automation(xtc_project *, const xtc_automation_point *);
xtc_result xtc_project_remove_automation(xtc_project *, uint64_t track, xtc_automation_parameter, uint64_t tick);
xtc_result xtc_project_automation_count(const xtc_project *, size_t *);
xtc_result xtc_project_get_automation(const xtc_project *, size_t, xtc_automation_point *);
/**
* @brief Sets a track's gain, pan, mute, and solo controls after validation.
*
* @param p Project model; borrowed for this call.
* @param track Stable track identifier.
* @param gain Linear gain, 0 to 4 for track controls.
* @param pan Pan position from -1 (left) to 1 (right).
* @param mute Track mute flag, 0 or 1.
* @param solo Track solo flag, 0 or 1.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_mix(xtc_project * p, uint64_t track, float gain, float pan, int mute, int solo);
/**
* @brief Inserts an automation point, replacing any point at the same track, parameter, and tick.
*
* @param p Project model; borrowed for this call.
* @param a Automation point to read or update.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_automation(xtc_project * p, const xtc_automation_point * a);
/**
* @brief Removes the automation point at the specified track, parameter, and tick.
*
* @param p Project model; borrowed for this call.
* @param track Stable track identifier.
* @param param Automation lane selector.
* @param tick Musical position in ticks.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_remove_automation(xtc_project * p, uint64_t track, xtc_automation_parameter param, uint64_t tick);
/**
* @brief Copies the number of automation points in the project.
*
* @param p Project model; borrowed for this call.
* @param n Receives the item count.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_automation_count(const xtc_project * p, size_t * n);
/**
* @brief Copies an automation point selected by dense list index.
*
* @param p Project model; borrowed for this call.
* @param i Zero-based index in the dense list.
* @param a Automation point to read or update.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_automation(const xtc_project * p, size_t i, xtc_automation_point * a);
/* Control-thread snapshot history; 32 edits plus baseline, no assets/DSP/GUI.
* record clones before changing history; failure retains all history. checkout
* clones adjacent state without moving cursor; accept only after installation.
@ -37,11 +79,45 @@ xtc_result xtc_project_get_automation(const xtc_project *, size_t, xtc_automatio
* Reset by creating a new history for New/Open. */
typedef struct xtc_history xtc_history;
xtc_result xtc_history_create(const xtc_project *, xtc_history **);
void xtc_history_destroy(xtc_history *);
xtc_result xtc_history_record(xtc_history *, const xtc_project *);
xtc_result xtc_history_checkout(const xtc_history *, int redo, xtc_project **);
xtc_result xtc_history_accept(xtc_history *, int redo);
/**
* @brief Creates snapshot history with an independent copy of the initial project.
*
* @param p Project model; borrowed for this call.
* @param out Receives the newly allocated object; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_history_create(const xtc_project * p, xtc_history ** out);
/**
* @brief Releases all retained snapshots and history storage; accepts NULL.
*
* @param h Snapshot history handle.
*/
void xtc_history_destroy(xtc_history * h);
/**
* @brief Appends a project snapshot, discarding redo states and evicting the oldest state if full.
*
* @param h Snapshot history handle.
* @param p Project model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_history_record(xtc_history * h, const xtc_project * p);
/**
* @brief Clones the adjacent undo or redo state without moving the history cursor.
*
* @param h Snapshot history handle.
* @param redo Zero for undo, one for redo.
* @param out Receives the newly allocated object; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_history_checkout(const xtc_history * h, int redo, xtc_project ** out);
/**
* @brief Moves the history cursor after the checked-out project has been installed.
*
* @param h Snapshot history handle.
* @param redo Zero for undo, one for redo.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_history_accept(xtc_history * h, int redo);
#ifdef __cplusplus
}

View file

@ -31,34 +31,110 @@ typedef struct {
char identifier[XTC_NATIVE_ID_BYTES]; /* Nonempty printable ASCII, terminated. */
xtc_native_kind kind;
/* Control thread. On error, any non-NULL *state is destroyed by host. */
/**
* @brief Allocates and prepares plugin state on the control thread.
*
* @param sample_rate Processing rate in hertz.
* @param max_frames Maximum frames per processing block.
* @param parameter Initial processor parameter.
* @param state Receives plugin state; the host destroys non-NULL state even on failure.
* @return XTC_OK on success, or a preparation error.
*/
xtc_result (*prepare)(double sample_rate, size_t max_frames, float parameter, void **state);
/* Single audio owner (or stopped). No allocation/free, I/O, locks or GUI. */
/**
* @brief Resets plugin state without allocation, I/O, locking, or GUI calls.
*
* @param state Plugin state owned exclusively by the audio consumer, or stopped.
*/
void (*reset)(void *state);
/**
* @brief Processes one block without allocation, I/O, locking, or GUI calls.
*
* @param state Plugin state owned exclusively by the audio consumer.
* @param block Borrowed buffers and events; write every output frame, including silence.
* @return XTC_OK on success, or a processing error that makes the host silence the block.
*/
xtc_result (*process)(void *state, const xtc_native_block *block);
/* Control thread after quiescence; must accept NULL. */
/**
* @brief Releases plugin state on the control thread after callback quiescence.
*
* @param state Plugin state to release; must accept NULL.
*/
void (*destroy)(void *state);
} xtc_native_descriptor;
typedef struct xtc_native xtc_native;
/* Descriptors are copied; callback code must remain loaded through destruction.
* Trusted statically linked C code, not a sandbox or stable shared-library ABI. */
xtc_result xtc_native_validate(const xtc_native_descriptor *);
xtc_result xtc_native_create(const xtc_native_descriptor *, double rate, size_t max_frames, float parameter,
/**
* @brief Checks the native descriptor's ABI, identifier, kind, and required callbacks.
*
* @param d Native processor descriptor.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_native_validate(const xtc_native_descriptor * d);
/**
* @brief Copies a native descriptor and prepares a processor instance on the control thread.
*
* @param d Native processor descriptor.
* @param rate Sample rate in hertz.
* @param max_frames Maximum frames per processing block.
* @param parameter Native parameter or automation lane selector, as applicable.
* @param out_processor Receives the newly allocated processor; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_native_create(const xtc_native_descriptor * d, double rate, size_t max_frames, float parameter,
xtc_native **out_processor);
void xtc_native_destroy(xtc_native *);
void xtc_native_reset(xtc_native *);
/**
* @brief Destroys native state and releases the host wrapper; accepts NULL.
*
* @param p Native processor instance.
*/
void xtc_native_destroy(xtc_native * p);
/**
* @brief Resets native state and transport continuity; accepts NULL.
*
* @param p Native processor instance.
*/
void xtc_native_reset(xtc_native * p);
/* Bounded, single-owner, realtime-safe host path (callbacks must obey contract).
* Invalid arguments leave state/output unchanged. Zero frames is a no-op.
* Discontinuity resets before processing. Callback failure silences this block
* and resets the processor. Successful nonfinite output samples become zero. */
xtc_result xtc_native_process(xtc_native *, const xtc_native_block *);
/**
* @brief Processes a bounded native block, resetting on discontinuity and silencing callback failures.
*
* @param p Native processor instance.
* @param b Borrowed processing buffers and block metadata.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_native_process(xtc_native * p, const xtc_native_block * b);
/* Graph accepts effects/sources; creates independent L/R instances on compile.
* For instruments, process events explicitly then feed the existing instrument
* source buffer through xtc_plan_process_sources/stereo. Graph add returns
* UNSUPPORTED for instrument descriptors (no event port in current topology).
* Native graphs are session-only: project save returns UNSUPPORTED. */
xtc_result xtc_graph_add_native(xtc_graph *, const xtc_native_descriptor *, float parameter, size_t *out_id);
xtc_result xtc_graph_get_native(const xtc_graph *, size_t node_id, xtc_native_descriptor *out_descriptor);
/**
* @brief Adds a validated native effect or source descriptor to the graph.
*
* @param graph Graph model; borrowed for this call.
* @param descriptor Native processor descriptor.
* @param parameter Native parameter or automation lane selector, as applicable.
* @param out_id Receives the id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_native(xtc_graph * graph, const xtc_native_descriptor * descriptor, float parameter, size_t *out_id);
/**
* @brief Copies the native descriptor of a node selected by stable identifier.
*
* @param graph Graph model; borrowed for this call.
* @param node_id Stable node identifier.
* @param out_descriptor Receives the descriptor.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_native(const xtc_graph * graph, size_t node_id, xtc_native_descriptor *out_descriptor);
#ifdef __cplusplus
}

View file

@ -33,39 +33,191 @@ typedef struct {
/* Single control-thread ownership. Names: nonempty UTF-8, <=63 bytes, no ASCII controls.
* IDs are monotonic, never reused, UINT64_MAX reserved. Snapshots are copies. */
/**
* @brief Allocates an empty project with an output-only graph policy and 120 BPM tempo.
*
* @param out_project Receives the newly allocated project; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_create(xtc_project **out_project);
/**
* @brief Releases a project and its graph; accepts NULL.
*
* @param project Project model; borrowed for this call.
*/
void xtc_project_destroy(xtc_project *project);
/**
* @brief Allocates an independent copy of project metadata and its graph.
*
* @param project Project model; borrowed for this call.
* @param out_project Receives the newly allocated project; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_clone(const xtc_project *project, xtc_project **out_project);
/**
* @brief Copies the current track and clip counts.
*
* @param project Project model; borrowed for this call.
* @param out_tracks Receives the tracks.
* @param out_clips Receives the clips.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_counts(const xtc_project *project, size_t *out_tracks, size_t *out_clips);
/**
* @brief Copies a track snapshot selected by dense list index.
*
* @param project Project model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param out_track Receives the track.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_track(const xtc_project *project, size_t index, xtc_track_info *out_track);
/**
* @brief Copies a clip snapshot selected by dense list index.
*
* @param project Project model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param out_clip Receives the clip.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_clip(const xtc_project *project, size_t index, xtc_clip_info *out_clip);
/**
* @brief Copies a clip snapshot selected by stable identifier.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param out_clip Receives the clip.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_find_clip(const xtc_project *project, uint64_t id, xtc_clip_info *out_clip);
/**
* @brief Appends a track with unity gain and a new stable identifier.
*
* @param project Project model; borrowed for this call.
* @param name NUL-terminated name.
* @param out_id Receives the id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_add_track(xtc_project *project, const char *name, uint64_t *out_id);
/* Removing a track also removes its clips. */
/**
* @brief Removes a track and its clips, notes, and automation.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_remove_track(xtc_project *project, uint64_t id);
/**
* @brief Appends a silent region on an existing track with a new stable identifier.
*
* @param project Project model; borrowed for this call.
* @param track_id Stable destination track identifier.
* @param name NUL-terminated name.
* @param start_tick Start position in musical ticks.
* @param length_tick Duration in musical ticks.
* @param out_id Receives the id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_add_clip(xtc_project *project, uint64_t track_id, const char *name, uint64_t start_tick,
uint64_t length_tick, uint64_t *out_id);
/**
* @brief Removes a clip and its notes.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_remove_clip(xtc_project *project, uint64_t id);
/* Atomic move/trim: source bounds remain unchanged, no destructive edits. */
/**
* @brief Atomically moves or trims a clip within its existing source bounds.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param track_id Stable destination track identifier.
* @param start_tick Start position in musical ticks.
* @param length_tick Duration in musical ticks.
* @param offset_tick Offset into the clip source in ticks.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_edit_clip(xtc_project *project, uint64_t id, uint64_t track_id, uint64_t start_tick,
uint64_t length_tick, uint64_t offset_tick);
/* Borrowed opaque graph valid until project mutation/destruction. set_graph
* copies before replacing; max 64 nodes, preserving IDs, edges and output policy. */
/**
* @brief Returns the project's borrowed graph, or NULL for a NULL project.
*
* @param project Project model; borrowed for this call.
* @return Borrowed graph valid until project mutation or destruction, or NULL for NULL.
*/
const xtc_graph *xtc_project_graph(const xtc_project *project);
/**
* @brief Copies and validates a graph before replacing the project's graph.
*
* @param project Project model; borrowed for this call.
* @param graph Graph model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_graph(xtc_project *project, const xtc_graph *graph);
/* Constant tempo in milli-BPM: 20000..300000, default 120000. */
/**
* @brief Returns the constant tempo in milli-BPM, or zero for a NULL project.
*
* @param project Project model; borrowed for this call.
* @return Tempo in milli-BPM, or zero for NULL.
*/
unsigned int xtc_project_tempo(const xtc_project *project);
/**
* @brief Sets the constant tempo within the range 20000 to 300000 milli-BPM.
*
* @param project Project model; borrowed for this call.
* @param milli_bpm Tempo in thousandths of a beat per minute.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_tempo(xtc_project *project, unsigned int milli_bpm);
/* Floor conversion, no accumulated drift. Rate: 1..768000. Tick <= MAX_TICK.
* Excess sample/tick positions return capacity; outputs unchanged on error. */
/**
* @brief Converts a musical tick to an absolute sample position, rounding down.
*
* @param project Project model; borrowed for this call.
* @param tick Musical position in ticks.
* @param sample_rate Sample rate in hertz.
* @param out_sample Receives the sample.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_tick_to_sample(const xtc_project *project, uint64_t tick, unsigned int sample_rate,
uint64_t *out_sample);
/**
* @brief Converts an absolute sample position to a musical tick, rounding down.
*
* @param project Project model; borrowed for this call.
* @param sample Absolute sample position.
* @param sample_rate Sample rate in hertz.
* @param out_tick Receives the tick.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_sample_to_tick(const xtc_project *project, uint64_t sample, unsigned int sample_rate,
uint64_t *out_tick);
/* Control-thread I/O. Versioned, bounded format; see docs/PROJECT_FORMAT.md.
* Load is transactional. Save fsyncs a same-directory temporary and renames it;
* failures before rename preserve the previous destination. No asset I/O. */
/**
* @brief Saves a versioned project through a synced temporary file and atomic rename.
*
* @param project Project model; borrowed for this call.
* @param path NUL-terminated file or asset path.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_save(const xtc_project *project, const char *path);
/**
* @brief Loads and validates a project before transactionally replacing the current state.
*
* @param project Project model; borrowed for this call.
* @param path NUL-terminated file or asset path.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_load(xtc_project *project, const char *path);
#ifdef __cplusplus

View file

@ -18,27 +18,103 @@ typedef struct {
/* Control thread only. A single control producer and single audio consumer.
* C99 with GCC/Clang lock-free __atomic builtins is required; unsupported
* targets return XTC_ERR_UNSUPPORTED. No ownership is shared with callers. */
/**
* @brief Allocates the single-producer, single-consumer realtime engine.
*
* @param sample_rate Sample rate in hertz.
* @param max_frames Maximum frames per processing block.
* @param out_engine Receives the newly allocated engine; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_create(double sample_rate, size_t max_frames, xtc_realtime **out_engine);
/* Requires the audio consumer to have stopped and joined. */
/**
* @brief Releases the engine after the audio consumer has stopped; accepts NULL.
*
* @param engine Realtime engine handle.
*/
void xtc_realtime_destroy(xtc_realtime *engine);
/* Compiles/copies a graph. One pending plan allowed; full mailbox returns
* XTC_ERR_CAPACITY without changing the active or pending plan. */
/**
* @brief Compiles and publishes a graph to the single pending-plan slot.
*
* @param engine Realtime engine handle.
* @param graph Graph model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_submit(xtc_realtime *engine, const xtc_graph *graph);
/* Frees acknowledged retired state, never the audio thread's active plan. */
/**
* @brief Frees acknowledged retired plans on the control thread.
*
* @param engine Realtime engine handle.
*/
void xtc_realtime_collect(xtc_realtime *engine);
/**
* @brief Copies processing counters, plan revisions, and the last processing result.
*
* @param engine Realtime engine handle.
* @param out_status Receives the status.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_get_status(const xtc_realtime *engine, xtc_realtime_status *out_status);
/* Audio consumer only. Adopts at most one plan per block, without allocation,
* freeing, locks or I/O. No plan means silence. On failure writes silence.
* frame_count must be <= configured max_frames and output must hold it.
* Oversized buffers are silenced too; callers must bound their own frame count.
* NULL input means silence. Zero frames is a no-op. */
xtc_result xtc_realtime_process_stereo(xtc_realtime *, const float *input, const float *audio_left,
/**
* @brief Adopts a pending plan and processes stereo sources without allocation or locking.
*
* @param engine Realtime engine handle.
* @param input Borrowed input sample buffer.
* @param audio_left Borrowed left timeline source buffer; NULL means silence.
* @param audio_right Borrowed right timeline source buffer; NULL means silence.
* @param midi_left Borrowed left instrument source buffer; NULL means silence.
* @param midi_right Borrowed right instrument source buffer; NULL means silence.
* @param left Destination left-channel buffer.
* @param right Destination right-channel buffer; NULL selects mono rendering.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_process_stereo(xtc_realtime * engine, const float *input, const float *audio_left,
const float *audio_right, const float *midi_left, const float *midi_right,
float *left, float *right, size_t frames);
/**
* @brief Processes mono timeline and instrument sources through the realtime engine.
*
* @param engine Realtime engine handle.
* @param input Borrowed input sample buffer.
* @param timeline Borrowed timeline source buffer; NULL means silence.
* @param instrument Borrowed instrument source buffer; NULL means silence.
* @param output Destination buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_process_sources(xtc_realtime *engine, const float *input, const float *timeline,
const float *instrument, float *output, size_t frames);
/**
* @brief Processes a mono block with timeline audio and no instrument source.
*
* @param engine Realtime engine handle.
* @param input Borrowed input sample buffer.
* @param timeline Borrowed timeline source buffer; NULL means silence.
* @param output Destination buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_process_audio(xtc_realtime *engine, const float *input, const float *timeline, float *output,
size_t frames);
/**
* @brief Processes a mono block with external input only.
*
* @param engine Realtime engine handle.
* @param input Borrowed input sample buffer.
* @param output Destination buffer with room for the requested frames.
* @param frame_count Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_process(xtc_realtime *engine, const float *input, float *output, size_t frame_count);
#ifdef __cplusplus

View file

@ -21,6 +21,12 @@ typedef enum {
XTC_ERR_FORMAT
} xtc_result;
/**
* @brief Returns a short human-readable name for a result code.
*
* @param result Result code to describe.
* @return Borrowed static string; never NULL.
*/
const char *xtc_result_string(xtc_result result);
#ifdef __cplusplus

View file

@ -21,6 +21,23 @@ struct xtc_audio_plan {
unsigned int rate;
};
/**
* @brief Imports an audio file as a timeline clip.
*
* Resolves path to an absolute reference, reads the WAV to determine its
* musical length, then transactionally clones the project, adds a clip, stores
* the asset reference, and adds an audio source -> output route when the graph
* has no audio source. The project is unchanged on failure.
*
* @param project Project to modify; must not be NULL.
* @param track Existing destination track ID.
* @param path Source WAV path; resolved with realpath() and must fit the asset path limit.
* @param start Clip start position in project ticks.
* @param out_clip Receives the new clip ID on success; unchanged on failure.
* @return XTC_OK on success, XTC_ERR_INVALID_ARGUMENT for NULL arguments, XTC_ERR_IO
* when the path cannot be resolved or read, XTC_ERR_CAPACITY when the absolute
* path is too long, or a WAV load or project edit error.
*/
xtc_result xtc_project_import_audio(xtc_project *project, uint64_t track, const char *path, uint64_t start,
uint64_t *out_clip)
{
@ -79,6 +96,11 @@ xtc_result xtc_project_import_audio(xtc_project *project, uint64_t track, const
return result;
}
/**
* @brief Releases an audio plan and all samples preloaded by it.
*
* @param plan Plan to destroy; NULL is accepted and does nothing.
*/
void xtc_audio_plan_destroy(xtc_audio_plan *plan)
{
size_t i;
@ -89,6 +111,21 @@ void xtc_audio_plan_destroy(xtc_audio_plan *plan)
free(plan);
}
/**
* @brief Preloads project audio into an immutable render plan.
*
* At most 64 distinct assets and 67,108,864 total mono frames are accepted.
* Missing or invalid assets are skipped and counted as issues rather than
* failing creation. The caller owns the returned plan and must release it with
* xtc_audio_plan_destroy().
*
* @param project Project whose clips are compiled; must not be NULL.
* @param rate Output sample rate in Hz, from 1 to 768000.
* @param out Receives the new plan on success and is set to NULL on failure.
* @return XTC_OK on success, XTC_ERR_INVALID_ARGUMENT for NULL arguments or an
* invalid rate, XTC_ERR_OUT_OF_MEMORY if allocation fails, XTC_ERR_CAPACITY
* for asset or frame limits, or an error from mix preparation or tick conversion.
*/
xtc_result xtc_audio_plan_create(const xtc_project *project, unsigned int rate, xtc_audio_plan **out)
{
xtc_audio_plan *plan;
@ -158,6 +195,14 @@ xtc_result xtc_audio_plan_create(const xtc_project *project, unsigned int rate,
return XTC_OK;
}
/**
* @brief Returns the number of assets that could not be loaded.
*
* @param plan Plan to query; NULL reports zero issues.
* @param path Receives a borrowed path to the first failed asset, or an empty
* string when there is none; may be NULL.
* @return Number of skipped assets; zero when plan is NULL.
*/
size_t xtc_audio_plan_issues(const xtc_audio_plan *plan, const char **path)
{
if (path)
@ -165,6 +210,20 @@ size_t xtc_audio_plan_issues(const xtc_audio_plan *plan, const char **path)
return plan ? plan->issues : 0;
}
/**
* @brief Renders a block of the plan at unity gain with linear rate conversion.
*
* Both buffers are replaced, not mixed into. Rendering performs no allocation,
* file access, or locking. Non-finite output samples are replaced with zero.
*
* @param plan Immutable plan to render; must not be NULL.
* @param position First output sample position on the project timeline.
* @param output Left or mono destination with room for frames samples.
* @param right Optional right destination; must differ from output, or NULL for mono.
* @param frames Number of samples to render, at most 65536.
* @return XTC_OK on success, or XTC_ERR_INVALID_ARGUMENT for NULL output, aliased
* stereo buffers, too many frames, or a position overflow.
*/
xtc_result xtc_audio_plan_render_stereo(const xtc_audio_plan *plan, uint64_t position, float *output, float *right,
size_t frames)
{
@ -206,6 +265,17 @@ xtc_result xtc_audio_plan_render_stereo(const xtc_audio_plan *plan, uint64_t pos
return XTC_OK;
}
/**
* @brief Renders a mono block of the plan.
*
* Equivalent to xtc_audio_plan_render_stereo() with no right channel.
*
* @param p Immutable plan to render; must not be NULL.
* @param position First output sample position on the project timeline.
* @param out Destination with room for frames samples.
* @param frames Number of samples to render, at most 65536.
* @return XTC_OK on success, or XTC_ERR_INVALID_ARGUMENT for invalid arguments.
*/
xtc_result xtc_audio_plan_render(const xtc_audio_plan *p, uint64_t position, float *out, size_t frames)
{
return xtc_audio_plan_render_stereo(p, position, out, NULL, frames);

View file

@ -15,6 +15,15 @@ struct xtc_recorder {
int finished;
};
/**
* @brief Allocates a bounded recording ring and opens a temporary mono float32 WAV file.
*
* @param path NUL-terminated file or asset path.
* @param rate Sample rate in hertz.
* @param capacity Recording ring capacity in frames, from 1 to 2097152.
* @param out Receives the newly allocated object; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_recorder_create(const char *path, unsigned int rate, size_t capacity, xtc_recorder **out)
{
xtc_recorder *r;
@ -67,6 +76,13 @@ xtc_result xtc_recorder_create(const char *path, unsigned int rate, size_t capac
return XTC_OK;
}
/**
* @brief Enqueues an entire audio block without blocking; overflow sets a sticky error.
*
* @param r Recorder handle.
* @param input Borrowed input sample buffer.
* @param frames Number of audio frames.
*/
void xtc_recorder_push(xtc_recorder *r, const float *input, size_t frames)
{
uint64_t write, read;
@ -85,6 +101,12 @@ void xtc_recorder_push(xtc_recorder *r, const float *input, size_t frames)
__atomic_store_n(&r->write_position, write + frames, __ATOMIC_RELEASE);
}
/**
* @brief Writes queued samples to the temporary WAV file on the control thread.
*
* @param r Recorder handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_recorder_drain(xtc_recorder *r)
{
uint64_t read, write;
@ -108,6 +130,13 @@ xtc_result xtc_recorder_drain(xtc_recorder *r)
return (xtc_result)__atomic_load_n(&r->error, __ATOMIC_ACQUIRE);
}
/**
* @brief Copies the number of accepted frames and returns the sticky recording status.
*
* @param r Recorder handle.
* @param frames Receives the accepted frame count.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_recorder_status(const xtc_recorder *r, uint64_t *frames)
{
if (!r || !frames)
@ -116,6 +145,12 @@ xtc_result xtc_recorder_status(const xtc_recorder *r, uint64_t *frames)
return (xtc_result)__atomic_load_n(&r->error, __ATOMIC_ACQUIRE);
}
/**
* @brief Drains and finalizes the WAV file, then renames it after producer quiescence.
*
* @param r Recorder handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_recorder_finish(xtc_recorder *r)
{
xtc_result result;
@ -137,6 +172,11 @@ xtc_result xtc_recorder_finish(xtc_recorder *r)
return failed ? XTC_ERR_IO : result;
}
/**
* @brief Releases recorder storage and removes any unfinished temporary file; accepts NULL.
*
* @param r Recorder handle.
*/
void xtc_recorder_destroy(xtc_recorder *r)
{
if (!r)

View file

@ -4,16 +4,34 @@
#include <stdlib.h>
#include <string.h>
/**
* @brief Reads an unsigned 32-bit integer from four little-endian bytes.
*
* @param p Byte buffer containing the little-endian integer.
* @return Decoded 32-bit value.
*/
static uint32_t u32(const unsigned char *p)
{
return (uint32_t)p[0] | (uint32_t)p[1] << 8U | (uint32_t)p[2] << 16U | (uint32_t)p[3] << 24U;
}
/**
* @brief Reads an unsigned 16-bit integer from two little-endian bytes.
*
* @param p Byte buffer containing the little-endian integer.
* @return Decoded 16-bit value.
*/
static unsigned int u16(const unsigned char *p)
{
return (unsigned int)p[0] | (unsigned int)p[1] << 8U;
}
/**
* @brief Writes an unsigned 32-bit integer as four little-endian bytes.
*
* @param p Destination buffer with room for four bytes.
* @param v Unsigned integer to encode.
*/
static void put32(unsigned char *p, uint32_t v)
{
size_t i;
@ -23,6 +41,14 @@ static void put32(unsigned char *p, uint32_t v)
}
}
/**
* @brief Writes a 44-byte mono float32 WAV header at the current file position.
*
* @param file Open file stream at the required read or write position.
* @param rate Sample rate in hertz.
* @param frames Number of audio frames.
* @return Nonzero if all header bytes were written, zero on a write failure.
*/
int xtc_wave_header(FILE *file, unsigned int rate, uint32_t frames)
{
unsigned char h[44] = {0};
@ -41,6 +67,14 @@ int xtc_wave_header(FILE *file, unsigned int rate, uint32_t frames)
return fwrite(h, 1, sizeof(h), file) == sizeof(h);
}
/**
* @brief Writes mono float32 samples in little-endian order, replacing nonfinite values with zero.
*
* @param file Open file stream at the required read or write position.
* @param input Borrowed input sample buffer.
* @param frames Number of audio frames.
* @return Nonzero if all samples were written, zero on a write failure.
*/
int xtc_wave_write(FILE *file, const float *input, size_t frames)
{
unsigned char bytes[4096];
@ -60,6 +94,13 @@ int xtc_wave_write(FILE *file, const float *input, size_t frames)
return 1;
}
/**
* @brief Loads a bounded PCM or float WAV file into caller-owned mono sample storage.
*
* @param path NUL-terminated file or asset path.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_wave_load(const char *path, xtc_wave *out)
{
FILE *f;

View file

@ -10,7 +10,30 @@ typedef struct {
unsigned int rate;
} xtc_wave;
/**
* @brief Loads a bounded PCM or float WAV file into caller-owned mono sample storage.
*
* @param path NUL-terminated file or asset path.
* @param out_wave Receives the wave.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_wave_load(const char *path, xtc_wave *out_wave);
/**
* @brief Writes a 44-byte mono float32 WAV header at the current file position.
*
* @param file Open file stream at the required read or write position.
* @param rate Sample rate in hertz.
* @param frames Number of audio frames.
* @return Nonzero if all header bytes were written, zero on a write failure.
*/
int xtc_wave_header(FILE *file, unsigned int rate, uint32_t frames);
/**
* @brief Writes mono float32 samples in little-endian order, replacing nonfinite values with zero.
*
* @param file Open file stream at the required read or write position.
* @param input Borrowed input sample buffer.
* @param frames Number of audio frames.
* @return Nonzero if all samples were written, zero on a write failure.
*/
int xtc_wave_write(FILE *file, const float *input, size_t frames);
#endif

View file

@ -26,6 +26,14 @@ struct xtc_backend {
int ready, running; /* Control thread only. */
};
/**
* @brief Decodes queued JACK MIDI and renders the instrument for the current audio block.
*
* @param backend Backend handle.
* @param position Absolute sample position at the start of the block.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result render_midi(xtc_backend *backend, uint64_t position, jack_nframes_t frames)
{
xtc_midi_event events[XTC_MIDI_MAX_EVENTS];
@ -70,6 +78,13 @@ static xtc_result render_midi(xtc_backend *backend, uint64_t position, jack_nfra
(size_t)frames);
}
/**
* @brief Runs the JACK audio callback, rendering sources, processing the graph, and publishing status.
*
* @param frames Number of audio frames.
* @param context Opaque callback context.
* @return Zero after publishing the callback result.
*/
static int process(jack_nframes_t frames, void *context)
{
xtc_backend *backend = context;
@ -145,12 +160,25 @@ static int process(jack_nframes_t frames, void *context)
return 0; /* Keep connected and silent on recoverable processing errors. */
}
/**
* @brief Marks server loss and clears running state when JACK shuts down.
*
* @param context Opaque callback context.
*/
static void shutdown_callback(void *context)
{
xtc_backend *backend = context;
__atomic_store_n(&backend->lost, 1U, __ATOMIC_RELEASE);
}
/**
* @brief Requests a MIDI panic when a connection to the MIDI input changes.
*
* @param source JACK source port identifier.
* @param destination JACK destination port identifier.
* @param connected Nonzero when the ports became connected.
* @param context Opaque callback context.
*/
static void port_connection(jack_port_id_t source, jack_port_id_t destination, int connected, void *context)
{
xtc_backend *backend = context;
@ -159,6 +187,13 @@ static void port_connection(jack_port_id_t source, jack_port_id_t destination, i
(void)__atomic_fetch_add(&backend->panic_requested, 1U, __ATOMIC_RELEASE);
}
/**
* @brief Records a JACK sample-rate change and disables processing until recovery.
*
* @param rate Sample rate in hertz.
* @param context Opaque callback context.
* @return Zero to acknowledge the rate notification.
*/
static int rate_callback(jack_nframes_t rate, void *context)
{
xtc_backend *backend = context;
@ -168,6 +203,13 @@ static int rate_callback(jack_nframes_t rate, void *context)
return 0;
}
/**
* @brief Records the JACK buffer size and reports whether it exceeds the configured limit.
*
* @param frames Number of audio frames.
* @param context Opaque callback context.
* @return Zero if the buffer fits, nonzero if it exceeds the configured capacity.
*/
static int buffer_callback(jack_nframes_t frames, void *context)
{
xtc_backend *backend = context;
@ -175,6 +217,12 @@ static int buffer_callback(jack_nframes_t frames, void *context)
return 0;
}
/**
* @brief Increments the xrun counter and requests a MIDI panic.
*
* @param context Opaque callback context.
* @return Zero to acknowledge the xrun notification.
*/
static int xrun_callback(void *context)
{
xtc_backend *backend = context;
@ -183,6 +231,13 @@ static int xrun_callback(void *context)
return 0;
}
/**
* @brief Opens a JACK client and registers its ports without starting the server or connecting routes.
*
* @param config Backend kind, client name, block capacity, and output channel configuration.
* @param out_backend Receives the newly allocated backend; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_open(const xtc_backend_config *config, xtc_backend **out_backend)
{
xtc_backend *backend;
@ -266,6 +321,12 @@ fail:
return result;
}
/**
* @brief Quiesces callbacks and releases the backend; failed cleanup leaves the handle owned by the caller.
*
* @param backend Backend handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_close(xtc_backend *backend)
{
if (backend == NULL)
@ -282,6 +343,12 @@ xtc_result xtc_backend_close(xtc_backend *backend)
return XTC_OK;
}
/**
* @brief Activates the backend and enables audio processing.
*
* @param backend Backend handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_start(xtc_backend *backend)
{
if (backend == NULL)
@ -300,6 +367,12 @@ xtc_result xtc_backend_start(xtc_backend *backend)
return XTC_OK;
}
/**
* @brief Deactivates the backend and establishes callback quiescence.
*
* @param backend Backend handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_stop(xtc_backend *backend)
{
if (backend == NULL)
@ -318,6 +391,13 @@ xtc_result xtc_backend_stop(xtc_backend *backend)
return XTC_OK;
}
/**
* @brief Changes the stopped backend's sample position and resets MIDI voices.
*
* @param backend Backend handle.
* @param sample Absolute sample position.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_seek(xtc_backend *backend, uint64_t sample)
{
if (backend == NULL || sample > UINT64_MAX - 65536U)
@ -330,6 +410,13 @@ xtc_result xtc_backend_seek(xtc_backend *backend, uint64_t sample)
return XTC_OK;
}
/**
* @brief Compiles and submits a graph while preserving the previous engine on failure.
*
* @param backend Backend handle.
* @param graph Graph model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph)
{
if (backend == NULL)
@ -358,6 +445,13 @@ xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph)
return xtc_realtime_submit(backend->engine, graph);
}
/**
* @brief Preloads project audio and MIDI and submits its graph while stopped.
*
* @param backend Backend handle.
* @param project Project model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_prepare_project(xtc_backend *backend, const xtc_project *project)
{
xtc_audio_plan *candidate = NULL;
@ -384,6 +478,12 @@ xtc_result xtc_backend_prepare_project(xtc_backend *backend, const xtc_project *
return XTC_OK;
}
/**
* @brief Returns the first asset issue path, or an empty string when none is available.
*
* @param backend Backend handle.
* @return Borrowed path string, or an empty string if no issue is available.
*/
const char *xtc_backend_asset_issue(const xtc_backend *backend)
{
const char *path = "";
@ -392,6 +492,13 @@ const char *xtc_backend_asset_issue(const xtc_backend *backend)
return path;
}
/**
* @brief Attaches or detaches a borrowed recorder while the backend is stopped.
*
* @param backend Backend handle.
* @param recorder Recorder handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_set_recorder(xtc_backend *backend, xtc_recorder *recorder)
{
if (!backend)
@ -405,6 +512,12 @@ xtc_result xtc_backend_set_recorder(xtc_backend *backend, xtc_recorder *recorder
return XTC_OK;
}
/**
* @brief Requests an instrument panic at the next audio block boundary.
*
* @param backend Backend handle.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_panic(xtc_backend *backend)
{
if (!backend)
@ -417,6 +530,13 @@ xtc_result xtc_backend_panic(xtc_backend *backend)
return XTC_OK;
}
/**
* @brief Returns the borrowed name of the backend's MIDI input port.
*
* @param backend Backend handle.
* @param out Receives the newly allocated object; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_midi_port_name(const xtc_backend *backend, const char **out)
{
if (!backend || !out)
@ -427,12 +547,24 @@ xtc_result xtc_backend_midi_port_name(const xtc_backend *backend, const char **o
return XTC_OK;
}
/**
* @brief Reclaims retired realtime plans on the control thread.
*
* @param backend Backend handle.
*/
void xtc_backend_collect(xtc_backend *backend)
{
if (backend != NULL)
xtc_realtime_collect(backend->engine);
}
/**
* @brief Copies individually coherent backend counters and transport state.
*
* @param backend Backend handle.
* @param out_status Receives the status.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_get_status(const xtc_backend *backend, xtc_backend_status *out_status)
{
xtc_realtime_status engine_status;
@ -461,6 +593,14 @@ xtc_result xtc_backend_get_status(const xtc_backend *backend, xtc_backend_status
return XTC_OK;
}
/**
* @brief Returns the borrowed name of the mono input or primary output port.
*
* @param backend Backend handle.
* @param direction Local port direction.
* @param out_name Receives the name.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_port_name(const xtc_backend *backend, xtc_port_direction direction, const char **out_name)
{
if (backend == NULL || out_name == NULL || (direction != XTC_PORT_INPUT && direction != XTC_PORT_OUTPUT))
@ -471,6 +611,14 @@ xtc_result xtc_backend_port_name(const xtc_backend *backend, xtc_port_direction
return *out_name == NULL ? XTC_ERR_IO : XTC_OK;
}
/**
* @brief Returns the borrowed name of an output channel port.
*
* @param backend Backend handle.
* @param channel Output channel index: 0 for primary, 1 for stereo right.
* @param out Receives the newly allocated object; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_output_name(const xtc_backend *backend, size_t channel, const char **out)
{
if (!backend || !out || channel > 1)
@ -483,6 +631,15 @@ xtc_result xtc_backend_output_name(const xtc_backend *backend, size_t channel, c
return *out ? XTC_OK : XTC_ERR_IO;
}
/**
* @brief Visits available audio ports matching the requested direction until the visitor stops.
*
* @param backend Backend handle.
* @param direction Local port direction.
* @param visitor Visitor called with each borrowed port name.
* @param context Opaque callback context.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_visit_ports(xtc_backend *backend, xtc_port_direction direction, xtc_backend_port_visitor visitor,
void *context)
{
@ -503,6 +660,15 @@ xtc_result xtc_backend_visit_ports(xtc_backend *backend, xtc_port_direction dire
return XTC_OK;
}
/**
* @brief Connects or disconnects a local audio port and a named remote port.
*
* @param backend Backend handle.
* @param direction Local port direction.
* @param remote Borrowed remote port name.
* @param disconnect Nonzero to disconnect, zero to connect.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result route(xtc_backend *backend, xtc_port_direction direction, const char *remote, int disconnect)
{
const char *own = NULL;
@ -522,17 +688,41 @@ static xtc_result route(xtc_backend *backend, xtc_port_direction direction, cons
return code == 0 || (!disconnect && code == EEXIST) ? XTC_OK : XTC_ERR_IO;
}
/**
* @brief Connects this client's port to a remote port with the opposite direction.
*
* @param backend Backend handle.
* @param direction Local port direction.
* @param remote Borrowed remote port name.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_connect(xtc_backend *backend, xtc_port_direction direction, const char *remote)
{
return route(backend, direction, remote, 0);
}
/**
* @brief Disconnects this client's port from a remote port.
*
* @param backend Backend handle.
* @param direction Local port direction.
* @param remote Borrowed remote port name.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_backend_disconnect(xtc_backend *backend, xtc_port_direction direction, const char *remote)
{
return route(backend, direction, remote, 1);
}
#else
/**
* @brief Reports that output name is unavailable without JACK support.
*
* @param b Unused in this build.
* @param c Unused in this build.
* @param out Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_output_name(const xtc_backend *b, size_t c, const char **out)
{
(void)b;
@ -541,12 +731,25 @@ xtc_result xtc_backend_output_name(const xtc_backend *b, size_t c, const char **
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that panic is unavailable without JACK support.
*
* @param backend Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_panic(xtc_backend *backend)
{
(void)backend;
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that midi port name is unavailable without JACK support.
*
* @param backend Unused in this build.
* @param out Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_midi_port_name(const xtc_backend *backend, const char **out)
{
(void)backend;
@ -554,6 +757,13 @@ xtc_result xtc_backend_midi_port_name(const xtc_backend *backend, const char **o
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that prepare project is unavailable without JACK support.
*
* @param backend Unused in this build.
* @param project Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_prepare_project(xtc_backend *backend, const xtc_project *project)
{
(void)backend;
@ -561,12 +771,25 @@ xtc_result xtc_backend_prepare_project(xtc_backend *backend, const xtc_project *
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Returns an empty issue string in builds without JACK support.
*
* @param backend Unused in this build.
* @return Borrowed empty static string.
*/
const char *xtc_backend_asset_issue(const xtc_backend *backend)
{
(void)backend;
return "";
}
/**
* @brief Reports that set recorder is unavailable without JACK support.
*
* @param backend Unused in this build.
* @param recorder Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_set_recorder(xtc_backend *backend, xtc_recorder *recorder)
{
(void)backend;
@ -574,6 +797,13 @@ xtc_result xtc_backend_set_recorder(xtc_backend *backend, xtc_recorder *recorder
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that seek is unavailable without JACK support.
*
* @param backend Unused in this build.
* @param sample Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_seek(xtc_backend *backend, uint64_t sample)
{
(void)backend;
@ -582,6 +812,13 @@ xtc_result xtc_backend_seek(xtc_backend *backend, uint64_t sample)
}
/* Keep core/offline builds available on machines without JACK headers. */
/**
* @brief Validates open arguments and reports that JACK support is unavailable.
*
* @param config Configuration to check; must not be NULL.
* @param out_backend Receives NULL; must not be NULL.
* @return XTC_ERR_INVALID_ARGUMENT for missing pointers, otherwise XTC_ERR_UNSUPPORTED; clears a supplied output handle.
*/
xtc_result xtc_backend_open(const xtc_backend_config *config, xtc_backend **out_backend)
{
if (out_backend == NULL)
@ -592,23 +829,48 @@ xtc_result xtc_backend_open(const xtc_backend_config *config, xtc_backend **out_
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Accepts a NULL backend in builds without JACK support.
*
* @param backend Handle to close; NULL is accepted.
* @return XTC_OK for NULL, otherwise XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_close(xtc_backend *backend)
{
return backend == NULL ? XTC_OK : XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that start is unavailable without JACK support.
*
* @param backend Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_start(xtc_backend *backend)
{
(void)backend;
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that stop is unavailable without JACK support.
*
* @param backend Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_stop(xtc_backend *backend)
{
(void)backend;
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that submit is unavailable without JACK support.
*
* @param backend Unused in this build.
* @param graph Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph)
{
(void)backend;
@ -616,11 +878,23 @@ xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph)
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Performs no collection in builds without JACK support.
*
* @param backend Unused in this build.
*/
void xtc_backend_collect(xtc_backend *backend)
{
(void)backend;
}
/**
* @brief Reports that get status is unavailable without JACK support.
*
* @param backend Unused in this build.
* @param out_status Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_get_status(const xtc_backend *backend, xtc_backend_status *out_status)
{
(void)backend;
@ -628,6 +902,14 @@ xtc_result xtc_backend_get_status(const xtc_backend *backend, xtc_backend_status
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that port name is unavailable without JACK support.
*
* @param backend Unused in this build.
* @param direction Unused in this build.
* @param out_name Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_port_name(const xtc_backend *backend, xtc_port_direction direction, const char **out_name)
{
(void)backend;
@ -636,6 +918,15 @@ xtc_result xtc_backend_port_name(const xtc_backend *backend, xtc_port_direction
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that visit ports is unavailable without JACK support.
*
* @param backend Unused in this build.
* @param direction Unused in this build.
* @param visitor Unused in this build.
* @param context Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_visit_ports(xtc_backend *backend, xtc_port_direction direction, xtc_backend_port_visitor visitor,
void *context)
{
@ -646,6 +937,14 @@ xtc_result xtc_backend_visit_ports(xtc_backend *backend, xtc_port_direction dire
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that connect is unavailable without JACK support.
*
* @param backend Unused in this build.
* @param direction Unused in this build.
* @param remote Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_connect(xtc_backend *backend, xtc_port_direction direction, const char *remote)
{
(void)backend;
@ -654,6 +953,14 @@ xtc_result xtc_backend_connect(xtc_backend *backend, xtc_port_direction directio
return XTC_ERR_UNSUPPORTED;
}
/**
* @brief Reports that disconnect is unavailable without JACK support.
*
* @param backend Unused in this build.
* @param direction Unused in this build.
* @param remote Unused in this build.
* @return XTC_ERR_UNSUPPORTED.
*/
xtc_result xtc_backend_disconnect(xtc_backend *backend, xtc_port_direction direction, const char *remote)
{
(void)backend;

View file

@ -6,6 +6,12 @@ struct xtc_app {
unsigned int reserved;
};
/**
* @brief Returns a short human-readable name for a result code.
*
* @param result Result code to describe.
* @return Borrowed static string; never NULL.
*/
const char *xtc_result_string(xtc_result result)
{
switch (result) {
@ -34,6 +40,12 @@ const char *xtc_result_string(xtc_result result)
}
}
/**
* @brief Allocates an application handle.
*
* @param out_app Receives the newly allocated app; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_app_create(xtc_app **out_app)
{
xtc_app *app;
@ -52,6 +64,11 @@ xtc_result xtc_app_create(xtc_app **out_app)
return XTC_OK;
}
/**
* @brief Releases an application handle; accepts NULL.
*
* @param app Application handle.
*/
void xtc_app_destroy(xtc_app *app)
{
free(app);

View file

@ -1,6 +1,16 @@
#include <stddef.h>
/* Kept as a separately testable DSP primitive. */
/**
* @brief Applies a constant gain to a block of mono samples.
*
* This is kept as a small, separately testable DSP primitive. The input and
* output buffers may alias. A NULL buffer makes the call a no-op.
*
* @param input Source samples; NULL is accepted.
* @param output Destination samples; NULL is accepted.
* @param frame_count Number of samples to process.
* @param gain Linear gain factor applied to every sample.
*/
void xtc_dsp_gain_process(const float *input, float *output, size_t frame_count, float gain)
{
size_t frame;

View file

@ -18,6 +18,11 @@ struct xtc_plan {
xtc_native **native;
};
/**
* @brief Releases a compiled plan and all resources it owns.
*
* @param plan Plan to destroy; NULL is accepted.
*/
void xtc_plan_destroy(xtc_plan *plan)
{
if (plan == NULL)
@ -39,6 +44,15 @@ void xtc_plan_destroy(xtc_plan *plan)
free(plan);
}
/**
* @brief Allocates an independent execution plan with preallocated processing buffers.
*
* @param graph Graph model; borrowed for this call.
* @param sample_rate Sample rate in hertz.
* @param max_frames Maximum frames per processing block.
* @param out_plan Receives the newly allocated plan; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_compile(const xtc_graph *graph, double sample_rate, size_t max_frames, xtc_plan **out_plan)
{
xtc_plan *plan;
@ -118,6 +132,12 @@ xtc_result xtc_graph_compile(const xtc_graph *graph, double sample_rate, size_t
return XTC_OK;
}
/**
* @brief Resets oscillator phases, meters, and native processor state without allocating.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_reset(xtc_plan *plan)
{
if (plan == NULL)
@ -133,6 +153,14 @@ xtc_result xtc_plan_reset(xtc_plan *plan)
return XTC_OK;
}
/**
* @brief Copies the last block's absolute peak for a meter node.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @param node_id Stable node identifier.
* @param out_peak Receives the peak.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_meter_peak(const xtc_plan *plan, size_t node_id, float *out_peak)
{
if (plan == NULL || out_peak == NULL)
@ -150,6 +178,20 @@ xtc_result xtc_plan_meter_peak(const xtc_plan *plan, size_t node_id, float *out_
return XTC_OK;
}
/**
* @brief Processes a block with separate stereo timeline and instrument sources.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @param input Borrowed input sample buffer.
* @param timeline Borrowed timeline source buffer; NULL means silence.
* @param timeline_right Borrowed right timeline source buffer; NULL means silence.
* @param instrument Borrowed instrument source buffer; NULL means silence.
* @param instrument_right Borrowed right instrument source buffer; NULL means silence.
* @param output Destination buffer with room for the requested frames.
* @param right Destination right-channel buffer; NULL selects mono rendering.
* @param frame_count Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_process_stereo(xtc_plan *plan, const float *input, const float *timeline,
const float *timeline_right, const float *instrument, const float *instrument_right,
float *output, float *right, size_t frame_count)
@ -231,6 +273,16 @@ xtc_result xtc_plan_process_stereo(xtc_plan *plan, const float *input, const flo
return XTC_OK;
}
/**
* @brief Compiles at 48000 Hz, applies input gain, renders once, and releases temporary state.
*
* @param graph Graph model; borrowed for this call.
* @param input Borrowed input sample buffer.
* @param output Destination buffer with room for the requested frames.
* @param frame_count Number of audio frames.
* @param input_gain Finite linear gain applied to external input before processing.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_process_offline(xtc_graph *graph, const float *input, float *output, size_t frame_count,
float input_gain)
{
@ -247,17 +299,47 @@ xtc_result xtc_graph_process_offline(xtc_graph *graph, const float *input, float
return result;
}
/**
* @brief Processes a mono block with external input and no timeline or instrument sources.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @param input Borrowed input sample buffer.
* @param output Destination buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_process(xtc_plan *plan, const float *input, float *output, size_t frames)
{
return xtc_plan_process_audio(plan, input, NULL, output, frames);
}
/**
* @brief Processes a mono block with timeline audio and no instrument source.
*
* @param plan Compiled graph plan and its single-owner runtime state.
* @param input Borrowed input sample buffer.
* @param timeline Borrowed timeline source buffer; NULL means silence.
* @param output Destination buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_process_audio(xtc_plan *plan, const float *input, const float *timeline, float *output,
size_t frames)
{
return xtc_plan_process_sources(plan, input, timeline, NULL, output, frames);
}
/**
* @brief Processes a mono block with separate timeline and instrument sources.
*
* @param p Compiled graph plan and its single-owner runtime state.
* @param in Borrowed mono input buffer; NULL means silence.
* @param audio Borrowed timeline source buffer; NULL means silence.
* @param midi Borrowed instrument source buffer; NULL means silence.
* @param out Destination sample buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_plan_process_sources(xtc_plan *p, const float *in, const float *audio, const float *midi, float *out,
size_t frames)
{

View file

@ -14,6 +14,21 @@ struct xtc_realtime {
unsigned int submitted_revision, pending_revision, active_revision;
};
/**
* @brief Creates a realtime engine for single-producer, single-consumer use.
*
* The engine starts without an active plan, so processing produces silence
* until a graph is submitted. The caller owns the new engine and must release
* it with xtc_realtime_destroy() after stopping the audio consumer.
*
* @param sample_rate Sample rate in Hz; must be finite and positive.
* @param max_frames Maximum frames per process call, from 1 to 65536.
* @param out_engine Receives the new engine on success and NULL on failure;
* must not be NULL.
* @return XTC_OK on success, XTC_ERR_INVALID_ARGUMENT for invalid limits,
* XTC_ERR_UNSUPPORTED if required atomics are not lock-free, or
* XTC_ERR_OUT_OF_MEMORY if allocation fails.
*/
xtc_result xtc_realtime_create(double sample_rate, size_t max_frames, xtc_realtime **out_engine)
{
xtc_realtime *engine;
@ -33,6 +48,11 @@ xtc_result xtc_realtime_create(double sample_rate, size_t max_frames, xtc_realti
return XTC_OK;
}
/**
* @brief Frees acknowledged retired plans on the control thread.
*
* @param engine Realtime engine handle.
*/
void xtc_realtime_collect(xtc_realtime *engine)
{
xtc_plan *retired;
@ -42,6 +62,11 @@ void xtc_realtime_collect(xtc_realtime *engine)
xtc_plan_destroy(retired);
}
/**
* @brief Releases the engine after the audio consumer has stopped; accepts NULL.
*
* @param engine Realtime engine handle.
*/
void xtc_realtime_destroy(xtc_realtime *engine)
{
if (engine == NULL)
@ -53,6 +78,13 @@ void xtc_realtime_destroy(xtc_realtime *engine)
free(engine);
}
/**
* @brief Compiles and publishes a graph to the single pending-plan slot.
*
* @param engine Realtime engine handle.
* @param graph Graph model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_submit(xtc_realtime *engine, const xtc_graph *graph)
{
xtc_plan *plan = NULL;
@ -74,6 +106,13 @@ xtc_result xtc_realtime_submit(xtc_realtime *engine, const xtc_graph *graph)
return XTC_OK;
}
/**
* @brief Copies processing counters, plan revisions, and the last processing result.
*
* @param engine Realtime engine handle.
* @param out_status Receives the status.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_get_status(const xtc_realtime *engine, xtc_realtime_status *out_status)
{
if (engine == NULL || out_status == NULL)
@ -85,6 +124,20 @@ xtc_result xtc_realtime_get_status(const xtc_realtime *engine, xtc_realtime_stat
return XTC_OK;
}
/**
* @brief Adopts a pending plan and processes stereo sources without allocation or locking.
*
* @param engine Realtime engine handle.
* @param input Borrowed input sample buffer.
* @param timeline Borrowed timeline source buffer; NULL means silence.
* @param timeline_right Borrowed right timeline source buffer; NULL means silence.
* @param instrument Borrowed instrument source buffer; NULL means silence.
* @param instrument_right Borrowed right instrument source buffer; NULL means silence.
* @param output Destination buffer with room for the requested frames.
* @param right Destination right-channel buffer; NULL selects mono rendering.
* @param frame_count Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_process_stereo(xtc_realtime *engine, const float *input, const float *timeline,
const float *timeline_right, const float *instrument,
const float *instrument_right, float *output, float *right, size_t frame_count)
@ -132,17 +185,47 @@ xtc_result xtc_realtime_process_stereo(xtc_realtime *engine, const float *input,
return result;
}
/**
* @brief Processes a mono block with external input only.
*
* @param engine Realtime engine handle.
* @param input Borrowed input sample buffer.
* @param output Destination buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_process(xtc_realtime *engine, const float *input, float *output, size_t frames)
{
return xtc_realtime_process_audio(engine, input, NULL, output, frames);
}
/**
* @brief Processes a mono block with timeline audio and no instrument source.
*
* @param engine Realtime engine handle.
* @param input Borrowed input sample buffer.
* @param timeline Borrowed timeline source buffer; NULL means silence.
* @param output Destination buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_process_audio(xtc_realtime *engine, const float *input, const float *timeline, float *output,
size_t frames)
{
return xtc_realtime_process_sources(engine, input, timeline, NULL, output, frames);
}
/**
* @brief Processes mono timeline and instrument sources through the realtime engine.
*
* @param p Realtime engine handle.
* @param in Borrowed mono input buffer; NULL means silence.
* @param audio Borrowed timeline source buffer; NULL means silence.
* @param midi Borrowed instrument source buffer; NULL means silence.
* @param out Destination sample buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_realtime_process_sources(xtc_realtime *p, const float *in, const float *audio, const float *midi,
float *out, size_t frames)
{

View file

@ -3,6 +3,13 @@
#include <stdlib.h>
#include <string.h>
/**
* @brief Allocates an empty graph with the requested node capacity.
*
* @param max_nodes Maximum number of graph nodes.
* @param out_graph Receives the newly allocated graph; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_create(size_t max_nodes, xtc_graph **out_graph)
{
xtc_graph *graph;
@ -25,6 +32,11 @@ xtc_result xtc_graph_create(size_t max_nodes, xtc_graph **out_graph)
return XTC_OK;
}
/**
* @brief Releases graph storage; accepts NULL.
*
* @param graph Graph model; borrowed for this call.
*/
void xtc_graph_destroy(xtc_graph *graph)
{
if (graph != NULL) {
@ -34,6 +46,13 @@ void xtc_graph_destroy(xtc_graph *graph)
}
}
/**
* @brief Finds a graph's dense node index by stable identifier.
*
* @param graph Graph model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return Dense node index, or SIZE_MAX if absent.
*/
static size_t node_index(const xtc_graph *graph, size_t id)
{
size_t i;
@ -43,6 +62,15 @@ static size_t node_index(const xtc_graph *graph, size_t id)
return SIZE_MAX;
}
/**
* @brief Appends a node after checking type, value, capacity, and identifier availability.
*
* @param graph Graph model; borrowed for this call.
* @param type Node type to create.
* @param value Value to encode, write, or apply.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result add_node(xtc_graph *graph, xtc_node_type type, float value, size_t *out_node_id)
{
if (graph == NULL || out_node_id == NULL || !isfinite(value))
@ -56,11 +84,27 @@ static xtc_result add_node(xtc_graph *graph, xtc_node_type type, float value, si
return XTC_OK;
}
/**
* @brief Adds a gain node with a finite linear gain value.
*
* @param graph Graph model; borrowed for this call.
* @param gain Linear gain, 0 to 4 for track controls.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_gain(xtc_graph *graph, float gain, size_t *out_node_id)
{
return add_node(graph, XTC_NODE_GAIN, gain, out_node_id);
}
/**
* @brief Adds a sine oscillator node with a positive frequency in hertz.
*
* @param graph Graph model; borrowed for this call.
* @param frequency Oscillator frequency in hertz.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_oscillator(xtc_graph *graph, float frequency, size_t *out_node_id)
{
if (frequency < 0.0f)
@ -68,26 +112,63 @@ xtc_result xtc_graph_add_oscillator(xtc_graph *graph, float frequency, size_t *o
return add_node(graph, XTC_NODE_OSCILLATOR, frequency, out_node_id);
}
/**
* @brief Adds a pass-through node that records the last block's absolute peak.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_meter(xtc_graph *graph, size_t *out_node_id)
{
return add_node(graph, XTC_NODE_METER, 0.0f, out_node_id);
}
/**
* @brief Adds a source node for the separately rendered MIDI instrument buffer.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_instrument(xtc_graph *graph, size_t *out_node_id)
{
return add_node(graph, XTC_NODE_INSTRUMENT, 0.0f, out_node_id);
}
/**
* @brief Adds a source node for the separately rendered timeline audio buffer.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_audio(xtc_graph *graph, size_t *out_node_id)
{
return add_node(graph, XTC_NODE_AUDIO, 0.0f, out_node_id);
}
/**
* @brief Adds an explicit output node included in the final mix.
*
* @param graph Graph model; borrowed for this call.
* @param out_node_id Receives the node id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_output(xtc_graph *graph, size_t *out_node_id)
{
return add_node(graph, XTC_NODE_OUTPUT, 0.0f, out_node_id);
}
/**
* @brief Copies the direction, signal type, and channel count of a node port.
*
* @param graph Graph model; borrowed for this call.
* @param node_id Stable node identifier.
* @param port_id Node port identifier.
* @param out_port Receives the port.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_port(const xtc_graph *graph, size_t node_id, size_t port_id, xtc_port_info *out_port)
{
xtc_node_type type;
@ -108,6 +189,13 @@ xtc_result xtc_graph_get_port(const xtc_graph *graph, size_t node_id, size_t por
return XTC_OK;
}
/**
* @brief Validates the graph and writes dense node indexes in topological order.
*
* @param graph Graph model; borrowed for this call.
* @param order Receives dense node indexes in topological order; must hold the graph node count.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_order(const xtc_graph *graph, size_t *order)
{
size_t indegree[XTC_MAX_NODES] = {0U};
@ -132,12 +220,28 @@ xtc_result xtc_graph_order(const xtc_graph *graph, size_t *order)
return tail == graph->node_count ? XTC_OK : XTC_ERR_GRAPH_CYCLE;
}
/**
* @brief Checks graph validity by attempting a topological ordering.
*
* @param graph Graph model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_validate(const xtc_graph *graph)
{
size_t order[XTC_MAX_NODES];
return xtc_graph_order(graph, order);
}
/**
* @brief Adds a validated audio connection while preserving an acyclic graph.
*
* @param graph Graph model; borrowed for this call.
* @param source_node Stable source node identifier.
* @param source_port Source output port identifier.
* @param destination_node Stable destination node identifier.
* @param destination_port Destination input port identifier.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_connect_ports(xtc_graph *graph, size_t source_node, size_t source_port, size_t destination_node,
size_t destination_port)
{
@ -167,11 +271,27 @@ xtc_result xtc_graph_connect_ports(xtc_graph *graph, size_t source_node, size_t
return result;
}
/**
* @brief Connects a source's output port 1 to a destination's input port 0.
*
* @param graph Graph model; borrowed for this call.
* @param source_node Stable source node identifier.
* @param destination_node Stable destination node identifier.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_connect(xtc_graph *graph, size_t source_node, size_t destination_node)
{
return xtc_graph_connect_ports(graph, source_node, 1U, destination_node, 0U);
}
/**
* @brief Copies the graph's node and connection counts.
*
* @param graph Graph model; borrowed for this call.
* @param out_nodes Receives the nodes.
* @param out_connections Receives the connections.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_counts(const xtc_graph *graph, size_t *out_nodes, size_t *out_connections)
{
if (graph == NULL || out_nodes == NULL || out_connections == NULL)
@ -181,6 +301,14 @@ xtc_result xtc_graph_get_counts(const xtc_graph *graph, size_t *out_nodes, size_
return XTC_OK;
}
/**
* @brief Copies node metadata selected by dense list index.
*
* @param graph Graph model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param out_node Receives the node.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_node(const xtc_graph *graph, size_t index, xtc_node_info *out_node)
{
if (graph == NULL || out_node == NULL)
@ -193,6 +321,14 @@ xtc_result xtc_graph_get_node(const xtc_graph *graph, size_t index, xtc_node_inf
return XTC_OK;
}
/**
* @brief Copies connection metadata selected by dense list index.
*
* @param graph Graph model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param out_connection Receives the connection.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_connection(const xtc_graph *graph, size_t index, xtc_connection_info *out_connection)
{
if (graph == NULL || out_connection == NULL)
@ -206,6 +342,13 @@ xtc_result xtc_graph_get_connection(const xtc_graph *graph, size_t index, xtc_co
return XTC_OK;
}
/**
* @brief Sets whether rendering requires explicit output nodes rather than terminal-node fallback.
*
* @param graph Graph model; borrowed for this call.
* @param required One to require explicit output nodes, zero to allow terminal-node fallback.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_require_output_nodes(xtc_graph *graph, int required)
{
if (graph == NULL || (required != 0 && required != 1))
@ -214,6 +357,13 @@ xtc_result xtc_graph_require_output_nodes(xtc_graph *graph, int required)
return XTC_OK;
}
/**
* @brief Removes a node and all connections touching it.
*
* @param graph Graph model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result remove_node(xtc_graph *graph, size_t id)
{
size_t index = node_index(graph, id), i, count = 0U;
@ -235,6 +385,13 @@ static xtc_result remove_node(xtc_graph *graph, size_t id)
return XTC_OK;
}
/**
* @brief Removes the connection matching all source and destination port fields.
*
* @param graph Graph model; borrowed for this call.
* @param connection Connection identifying both node and port pairs.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result disconnect_ports(xtc_graph *graph, const xtc_connection_info *connection)
{
size_t i;
@ -260,6 +417,15 @@ static xtc_result disconnect_ports(xtc_graph *graph, const xtc_connection_info *
return XTC_ERR_NOT_FOUND;
}
/**
* @brief Applies an edit to a new graph copy, leaving the source unchanged.
*
* @param source Graph model; borrowed for this call.
* @param edit Graph edit command.
* @param out_graph Receives the newly allocated graph; caller owns it on success.
* @param out_added_id Receives the added id.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_apply_edit(const xtc_graph *source, const xtc_graph_edit *edit, xtc_graph **out_graph,
size_t *out_added_id)
{
@ -331,6 +497,15 @@ xtc_result xtc_graph_apply_edit(const xtc_graph *source, const xtc_graph_edit *e
return XTC_OK;
}
/**
* @brief Adds a validated native effect or source descriptor to the graph.
*
* @param graph Graph model; borrowed for this call.
* @param descriptor Native processor descriptor.
* @param value Value to encode, write, or apply.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_add_native(xtc_graph *graph, const xtc_native_descriptor *descriptor, float value, size_t *out)
{
xtc_result result = xtc_native_validate(descriptor);
@ -344,6 +519,14 @@ xtc_result xtc_graph_add_native(xtc_graph *graph, const xtc_native_descriptor *d
return result;
}
/**
* @brief Copies the native descriptor of a node selected by stable identifier.
*
* @param graph Graph model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_get_native(const xtc_graph *graph, size_t id, xtc_native_descriptor *out)
{
size_t i;

View file

@ -26,5 +26,12 @@ struct xtc_graph {
xtc_edge *edges;
};
/**
* @brief Validates the graph and writes dense node indexes in topological order.
*
* @param graph Graph model; borrowed for this call.
* @param order Receives dense node indexes in topological order; must hold the graph node count.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_graph_order(const xtc_graph *graph, size_t *order);
#endif

View file

@ -9,6 +9,12 @@
namespace {
constexpr int node_width = 150, node_height = 74;
/**
* @brief Returns the display label for a node type.
*
* @param type Node type to describe.
* @return Borrowed static string; never NULL, falling back to "Unknown".
*/
const char *node_name(xtc_node_type type)
{
switch (type) {
@ -31,6 +37,21 @@ const char *node_name(xtc_node_type type)
}
} // namespace
/**
* @brief Constructs the widget and initializes its cached layout from the graph.
*
* The graph, handler, context, and feedback box are stored without taking
* ownership. Layout population is delegated to set_graph().
*
* @param left Left coordinate in FLTK screen space.
* @param top Top coordinate in FLTK screen space.
* @param width Widget width in pixels.
* @param height Widget height in pixels.
* @param graph Borrowed graph snapshot; must outlive the widget's use of it.
* @param handler Edit callback invoked for every graph mutation.
* @param context Opaque pointer passed back to @p handler; may be NULL.
* @param feedback_box Borrowed label used to report edit results.
*/
GraphWidget::GraphWidget(int left, int top, int width, int height, const xtc_graph *graph, EditHandler handler,
void *context, Fl_Box *feedback_box)
: Fl_Widget(left, top, width, height), graph_(graph), handler_(handler), context_(context), feedback_(feedback_box)
@ -38,6 +59,16 @@ GraphWidget::GraphWidget(int left, int top, int width, int height, const xtc_gra
set_graph(graph);
}
/**
* @brief Replaces the graph snapshot and rebuilds cached node positions.
*
* Existing positions are preserved by node ID. New nodes get grid slots that do
* not overlap previously known nodes. The selection is dropped when the selected
* node or wire is no longer present. The widget does not take ownership of the
* graph.
*
* @param graph Borrowed graph that must outlive the widget's use of it.
*/
void GraphWidget::set_graph(const xtc_graph *graph)
{
graph_ = graph;
@ -86,6 +117,14 @@ void GraphWidget::set_graph(const xtc_graph *graph)
redraw();
}
/**
* @brief Looks up the cached position of a node.
*
* @param id Stable node identifier.
* @param left Receives the left position in widget-local coordinates.
* @param top Receives the top position in widget-local coordinates.
* @return True when the node has a cached position, false otherwise.
*/
bool GraphWidget::node_position(size_t id, int &left, int &top) const
{
for (size_t i = 0U; i < count_; ++i)
@ -97,6 +136,16 @@ bool GraphWidget::node_position(size_t id, int &left, int &top) const
return false;
}
/**
* @brief Moves a node within the widget, clamped to the visible bounds.
*
* The node keeps a 12-pixel margin from the widget edges.
*
* @param id Stable node identifier.
* @param left Requested left position in widget-local coordinates.
* @param top Requested top position in widget-local coordinates.
* @return True when the node has a cached position, false otherwise.
*/
bool GraphWidget::move_node(size_t id, int left, int top)
{
for (size_t i = 0U; i < count_; ++i)
@ -109,6 +158,12 @@ bool GraphWidget::move_node(size_t id, int left, int top)
return false;
}
/**
* @brief Selects a node by ID and clears any wire selection.
*
* @param id Stable node identifier.
* @return True when the node has a cached position, false otherwise.
*/
bool GraphWidget::select_node(size_t id)
{
int left, top;
@ -120,6 +175,13 @@ bool GraphWidget::select_node(size_t id)
return true;
}
/**
* @brief Selects a wire between two nodes and clears the node selection.
*
* @param source Source node identifier.
* @param destination Destination node identifier.
* @return True when a matching connection exists, false otherwise.
*/
bool GraphWidget::select_connection(size_t source, size_t destination)
{
size_t nodes, edges;
@ -139,6 +201,12 @@ bool GraphWidget::select_connection(size_t source, size_t destination)
return false;
}
/**
* @brief Shows the outcome of an edit in the feedback label.
*
* @param result Result returned by the edit handler.
* @param success Text shown when @p result is XTC_OK; must be valid then and is ignored otherwise.
*/
void GraphWidget::feedback(xtc_result result, const char *success)
{
char text[256];
@ -153,6 +221,14 @@ void GraphWidget::feedback(xtc_result result, const char *success)
feedback_->copy_label(text);
}
/**
* @brief Requests creation of a node through the edit handler.
*
* Selects the new node when the edit succeeds.
*
* @param type Kind of node to add.
* @return Result reported by the edit handler.
*/
xtc_result GraphWidget::add_node(xtc_node_type type)
{
xtc_graph_edit edit{};
@ -167,6 +243,12 @@ xtc_result GraphWidget::add_node(xtc_node_type type)
return result;
}
/**
* @brief Requests a connection between two ports through the edit handler.
*
* @param connection Connection describing the source and destination node/port pairs.
* @return Result reported by the edit handler.
*/
xtc_result GraphWidget::connect_ports(const xtc_connection_info &connection)
{
xtc_graph_edit edit{};
@ -177,6 +259,12 @@ xtc_result GraphWidget::connect_ports(const xtc_connection_info &connection)
return result;
}
/**
* @brief Requests removal of the selected wire or node through the edit handler.
*
* @return Result reported by the edit handler, or XTC_ERR_INVALID_STATE when
* nothing is selected.
*/
xtc_result GraphWidget::delete_selected()
{
xtc_graph_edit edit{};
@ -195,6 +283,15 @@ xtc_result GraphWidget::delete_selected()
return result;
}
/**
* @brief Computes the widget-local center of a node port.
*
* @param id Stable node identifier.
* @param port Port index; 0 selects the input and 1 the output.
* @param left Receives the port center x coordinate.
* @param top Receives the port center y coordinate.
* @return True when the node and port exist, false otherwise.
*/
bool GraphWidget::port_position(size_t id, size_t port, int &left, int &top) const
{
xtc_port_info info{};
@ -205,6 +302,18 @@ bool GraphWidget::port_position(size_t id, size_t port, int &left, int &top) con
return true;
}
/**
* @brief Finds the port nearest to a widget-local point.
*
* Searches nodes in reverse draw order and accepts points within 9 pixels of a
* port center.
*
* @param left Widget-local x coordinate to test.
* @param top Widget-local y coordinate to test.
* @param node Receives the matching node ID when true is returned.
* @param port Receives the matching port index (0 input, 1 output) when true is returned.
* @return True when a port is within range, false otherwise.
*/
bool GraphWidget::hit_port(int left, int top, size_t &node, size_t &port) const
{
for (size_t i = count_; i > 0U; --i)
@ -220,6 +329,9 @@ bool GraphWidget::hit_port(int left, int top, size_t &node, size_t &port) const
return false;
}
/**
* @brief Draws the background, wires, nodes, ports, and any pending wire.
*/
void GraphWidget::draw()
{
fl_push_clip(x(), y(), w(), h());
@ -286,6 +398,13 @@ void GraphWidget::draw()
fl_pop_clip();
}
/**
* @brief Handles mouse and keyboard events for selection, dragging, and wiring.
*
* @param event FLTK event code.
* @return 1 when the event was consumed, 0 for unsupported left-button input,
* or the result of Fl_Widget::handle() for unhandled events.
*/
int GraphWidget::handle(int event)
{
const int left = Fl::event_x() - x(), top = Fl::event_y() - y();

View file

@ -9,10 +9,36 @@ class Fl_Box;
/* GUI positions/selection only. Domain snapshots and edits stay in the C API. */
class GraphWidget : public Fl_Widget {
public:
/**
* @brief Applies a graph edit using an opaque context and optional added-node output.
*
* The handler receives the context, edit command, and output identifier in that order.
* @return XTC_OK on success, or an edit error.
*/
using EditHandler = xtc_result (*)(void *, const xtc_graph_edit &, size_t *);
/**
* @brief Constructs a graph widget bound to a graph and an edit callback.
*
* The widget stores the graph, handler, context, and feedback box without
* taking ownership of any of them.
*
* @param x Left coordinate in FLTK screen space.
* @param y Top coordinate in FLTK screen space.
* @param width Widget width in pixels.
* @param height Widget height in pixels.
* @param graph Borrowed graph snapshot; must outlive the widget's use of it.
* @param handler Edit callback invoked for every graph mutation.
* @param context Opaque pointer passed back to @p handler; may be NULL.
* @param feedback Borrowed label used to report edit results; must outlive the widget.
*/
GraphWidget(int x, int y, int width, int height, const xtc_graph *graph, EditHandler handler, void *context,
Fl_Box *feedback);
/**
* @brief Clears cached node positions, selection, and interaction state.
*
* Does not change the borrowed graph pointer or the graph's own layout.
*/
void reset_layout()
{
count_ = 0;
@ -20,23 +46,105 @@ class GraphWidget : public Fl_Widget {
edge_selected_ = dragging_ = wiring_ = false;
}
/**
* @brief Replaces the graph snapshot and rebuilds cached node positions.
*
* Existing positions are preserved by node ID; new nodes are placed on a grid
* that avoids overlapping previously known nodes. Selections that no longer
* exist are cleared. The widget does not take ownership of @p graph.
*
* @param graph Borrowed graph that must outlive the widget's use of it.
*/
void set_graph(const xtc_graph *graph);
/**
* @brief Returns the graph snapshot currently displayed.
*
* The returned pointer is borrowed and valid for GUI thread use only.
*
* @return Borrowed graph pointer; may be NULL when none was supplied.
*/
const xtc_graph *graph() const
{
return graph_;
} // Borrowed, GUI thread only.
/**
* @brief Requests creation of a node through the edit handler.
*
* Selects the new node when the edit succeeds.
*
* @param type Kind of node to add.
* @return Result reported by the edit handler.
*/
xtc_result add_node(xtc_node_type type);
/**
* @brief Requests a connection between two ports through the edit handler.
*
* @param connection Connection describing the source and destination node/port pairs.
* @return Result reported by the edit handler.
*/
xtc_result connect_ports(const xtc_connection_info &connection);
/**
* @brief Requests removal of the selected wire or node through the edit handler.
*
* @return Result reported by the edit handler, or XTC_ERR_INVALID_STATE when
* nothing is selected.
*/
xtc_result delete_selected();
/**
* @brief Selects a node by ID and clears any wire selection.
*
* @param id Stable node identifier.
* @return True when the node has a cached position, false otherwise.
*/
bool select_node(size_t id);
/**
* @brief Selects a wire between two nodes and clears the node selection.
*
* @param source Source node identifier.
* @param destination Destination node identifier.
* @return True when a matching connection exists, false otherwise.
*/
bool select_connection(size_t source, size_t destination);
/**
* @brief Moves a node within the widget, clamped to the visible bounds.
*
* @param id Stable node identifier.
* @param left Requested left position in widget-local coordinates.
* @param top Requested top position in widget-local coordinates.
* @return True when the node has a cached position, false otherwise.
*/
bool move_node(size_t id, int left, int top);
/**
* @brief Looks up the cached position of a node.
*
* @param id Stable node identifier.
* @param left Receives the left position in widget-local coordinates.
* @param top Receives the top position in widget-local coordinates.
* @return True when the node has a cached position, false otherwise.
*/
bool node_position(size_t id, int &left, int &top) const;
/**
* @brief Handles mouse and keyboard events for selection, dragging, and wiring.
*
* @param event FLTK event code.
* @return 1 when the event was consumed, 0 for unsupported left-button input,
* or the result of Fl_Widget::handle() for unhandled events.
*/
int handle(int event) override;
protected:
/**
* @brief Draws the background, wires, nodes, ports, and any pending wire.
*/
void draw() override;
private:
@ -57,8 +165,37 @@ class GraphWidget : public Fl_Widget {
bool dragging_ = false, wiring_ = false;
size_t wire_node_ = SIZE_MAX, wire_port_ = 0U;
int drag_x_ = 0, drag_y_ = 0, mouse_x_ = 0, mouse_y_ = 0;
/**
* @brief Shows the outcome of an edit in the feedback label.
*
* @param result Result returned by the edit handler.
* @param success Text shown when @p result is XTC_OK; must be valid then and is ignored otherwise.
*/
void feedback(xtc_result result, const char *success);
/**
* @brief Finds the port nearest to a widget-local point.
*
* Searches nodes in reverse draw order and accepts points within 9 pixels of a
* port center.
*
* @param left Widget-local x coordinate to test.
* @param top Widget-local y coordinate to test.
* @param node Receives the matching node ID when true is returned.
* @param port Receives the matching port index (0 input, 1 output) when true is returned.
* @return True when a port is within range, false otherwise.
*/
bool hit_port(int left, int top, size_t &node, size_t &port) const;
/**
* @brief Computes the widget-local center of a node port.
*
* @param id Stable node identifier.
* @param port Port index; 0 selects the input and 1 the output.
* @param left Receives the port center x coordinate.
* @param top Receives the port center y coordinate.
* @return True when the node and port exist, false otherwise.
*/
bool port_position(size_t id, size_t port, int &left, int &top) const;
};
#endif

View file

@ -6,6 +6,13 @@
#include "xtc/xtc_version.h"
#include <cstring>
/**
* @brief Runs the FLTK application and reports command-line or startup failures.
*
* @param argc Number of command-line arguments including the program name.
* @param argv Command-line argument vector.
* @return EXIT_SUCCESS after the event loop exits, or EXIT_FAILURE for invalid usage or startup failure.
*/
int main(int argc, char **argv)
{
if (argc == 2 && !std::strcmp(argv[1], "--version")) {

View file

@ -19,6 +19,15 @@
#include <stdexcept>
namespace {
/**
* @brief Updates a box label only when the text differs.
*
* Avoids redundant relayout and redraw when status text is refreshed
* repeatedly. The box takes a copy of the text.
*
* @param box Box whose label is updated; must not be NULL.
* @param text New label text; must not be NULL.
*/
void set_label(Fl_Box *box, const char *text)
{
if (box->label() == nullptr || std::strcmp(box->label(), text) != 0)
@ -26,6 +35,16 @@ void set_label(Fl_Box *box, const char *text)
}
} // namespace
/**
* @brief Constructs the window, its widgets, and the initial project.
*
* Creates an empty project and undo history, opens the audio backend, and
* schedules the periodic status timer. Throws std::runtime_error when the
* initial project or history cannot be created.
*
* @param prompt_unsaved Whether open, new, and close actions ask before
* discarding unsaved changes.
*/
MainWindow::MainWindow(bool prompt_unsaved)
: Fl_Double_Window(1000, 680, XTC_PROJECT_NAME " — modular audio workstation"), prompt_unsaved_(prompt_unsaved)
{
@ -229,6 +248,12 @@ MainWindow::MainWindow(bool prompt_unsaved)
Fl::add_timeout(0.1, timer_callback, this);
}
/**
* @brief Removes the status timer and releases backend and recording state.
*
* A backend that fails to close is intentionally retained with its recorder
* until process exit so active callbacks never observe freed state.
*/
MainWindow::~MainWindow()
{
Fl::remove_timeout(timer_callback, this);
@ -244,6 +269,13 @@ MainWindow::~MainWindow()
}
}
/**
* @brief Shows an operation failure and a context-sensitive hint.
*
* @param operation Human-readable operation name; names containing
* "Project" select a file and permission hint.
* @param result Failing result code whose name is included in the message.
*/
void MainWindow::report_error(const char *operation, xtc_result result)
{
char text[384];
@ -255,6 +287,20 @@ void MainWindow::report_error(const char *operation, xtc_result result)
set_label(error_, text);
}
/**
* @brief Edit handler passed to the modular graph widget.
*
* Applies the edit to a candidate graph, clones the project around it, and
* submits the new graph to the engine when connected. The live project is
* replaced only after every step succeeds.
*
* @param context MainWindow * passed as the graph widget owner context.
* @param edit Graph edit to apply.
* @param out_added_id Optional output receiving the new node ID when the edit
* adds a node; untouched otherwise or when nullptr.
* @return XTC_OK on success, XTC_ERR_INVALID_STATE while recording, or an
* error from the graph, project, or backend operation.
*/
xtc_result MainWindow::edit_graph(void *context, const xtc_graph_edit &edit, size_t *out_added_id)
{
auto *window = static_cast<MainWindow *>(context);
@ -286,6 +332,13 @@ xtc_result MainWindow::edit_graph(void *context, const xtc_graph_edit &edit, siz
return XTC_OK;
}
/**
* @brief Opens the JACK backend and submits the current graph.
*
* Uses the default backend configuration and leaves the engine stopped.
* Failure is reported and leaves the backend disconnected; a handle that
* opened but failed to accept the graph is retained for reconnect.
*/
void MainWindow::open_backend()
{
xtc_backend_config config = {XTC_BACKEND_JACK, XTC_PROJECT_NAME, 65536U, 2U};
@ -305,6 +358,13 @@ void MainWindow::open_backend()
refresh_status();
}
/**
* @brief Starts the audio engine with the current project.
*
* Does nothing when already running. Reports an error when disconnected.
* Otherwise prepares the project and starts the engine. Missing or
* unreadable audio is reported as a warning after a successful start.
*/
void MainWindow::start_engine()
{
if (!ready_) {
@ -331,6 +391,11 @@ void MainWindow::start_engine()
refresh_status();
}
/**
* @brief Stops the audio engine, finalizing any active recording.
*
* Does nothing when disconnected. Failures are reported in the window.
*/
void MainWindow::stop_engine()
{
if (!ready_)
@ -346,6 +411,12 @@ void MainWindow::stop_engine()
refresh_status();
}
/**
* @brief Closes and reopens the JACK backend, leaving the engine stopped.
*
* Refuses to run while recording. If closing fails the existing handle is
* retained for a later retry and the failure is reported.
*/
void MainWindow::reconnect_engine()
{
if (recorder_) {
@ -363,6 +434,14 @@ void MainWindow::reconnect_engine()
open_backend();
}
/**
* @brief Polls the backend and updates labels, meters, and button states.
*
* When disconnected, shows offline text and disables transport controls.
* Otherwise updates the sequencer position, mixer peaks, status line, and
* error indicators from the latest engine status, then collects backend
* work.
*/
void MainWindow::refresh_status()
{
if (mixer_)
@ -432,6 +511,15 @@ void MainWindow::refresh_status()
stop_->deactivate();
}
/**
* @brief Attempts to close the window after discarding unsaved changes.
*
* Refuses while recording. On success closes the backend, removes the status
* timer, hides the piano-roll window, and hides this window.
*
* @return True when the window closed; false when recording is active, the
* user cancels discarding, or the backend cannot close.
*/
bool MainWindow::close_window()
{
if (recorder_) {
@ -456,6 +544,14 @@ bool MainWindow::close_window()
return true;
}
/**
* @brief Handles FLTK events for the window.
*
* Space without Ctrl/Alt/Meta toggles the audio engine and is consumed.
*
* @param event FLTK event code, such as FL_SHORTCUT.
* @return 1 when Space was consumed; otherwise the Fl_Double_Window result.
*/
int MainWindow::handle(int event)
{
if (event == FL_SHORTCUT && Fl::event_key() == ' ' && (Fl::event_state() & (FL_CTRL | FL_ALT | FL_META)) == 0) {
@ -468,26 +564,53 @@ int MainWindow::handle(int event)
return Fl_Double_Window::handle(event);
}
/**
* @brief FLTK callback for the Play menu item and button.
*
* @param context MainWindow * whose engine is started.
*/
void MainWindow::play_callback(Fl_Widget *, void *context)
{
static_cast<MainWindow *>(context)->start_engine();
}
/**
* @brief FLTK callback for the Stop menu item and button.
*
* @param context MainWindow * whose engine is stopped.
*/
void MainWindow::stop_callback(Fl_Widget *, void *context)
{
static_cast<MainWindow *>(context)->stop_engine();
}
/**
* @brief FLTK callback for the Reconnect menu item and button.
*
* @param context MainWindow * whose backend is reopened.
*/
void MainWindow::reconnect_callback(Fl_Widget *, void *context)
{
static_cast<MainWindow *>(context)->reconnect_engine();
}
/**
* @brief FLTK callback for the window close box, File/Quit, and close events.
*
* @param context MainWindow * whose close is requested; a refused close
* simply leaves the window open.
*/
void MainWindow::close_callback(Fl_Widget *, void *context)
{
(void)static_cast<MainWindow *>(context)->close_window();
}
/**
* @brief FLTK menu callback that selects a tab page in the main window.
*
* @param widget Triggering menu item; window() must return the MainWindow.
* @param view Fl_Widget * tab page to make current, not the MainWindow.
*/
void MainWindow::view_callback(Fl_Widget *widget, void *view)
{
auto *window = static_cast<MainWindow *>(widget->window());
@ -495,12 +618,25 @@ void MainWindow::view_callback(Fl_Widget *widget, void *view)
window->tabs_->redraw();
}
/**
* @brief FLTK help callback that shows the application name and version.
*
*/
void MainWindow::about_callback(Fl_Widget *, void *)
{
fl_message("%s %s\nModular audio workstation\nPhase 9: native C processors and Linux release preparation.",
XTC_PROJECT_NAME, XTC_VERSION_STRING);
}
/**
* @brief FLTK timeout callback that refreshes status and supervises recording.
*
* While recording, drains the recorder and stops the engine if draining
* fails or the backend reports a lost server, sample-rate change, or
* processing error. Reschedules itself every 0.1 seconds until closing.
*
* @param context MainWindow * registered with Fl::add_timeout().
*/
void MainWindow::timer_callback(void *context)
{
auto *window = static_cast<MainWindow *>(context);
@ -516,6 +652,12 @@ void MainWindow::timer_callback(void *context)
Fl::repeat_timeout(0.1, timer_callback, context);
}
/**
* @brief Records the current project in history and marks the window dirty.
*
* Dependent widgets are refreshed with the current project and graph, and
* the title gains the unsaved marker. A history failure is reported.
*/
void MainWindow::mark_dirty()
{
xtc_result history_result = xtc_history_record(history_.get(), project_.get());
@ -529,12 +671,29 @@ void MainWindow::mark_dirty()
copy_label("xtc * — unsaved changes");
}
/**
* @brief Asks whether unsaved changes may be discarded.
*
* @return True when the project is clean, prompting is disabled, or the user
* chose Discard; false when the user cancels.
*/
bool MainWindow::discard_changes()
{
return !dirty_ || !prompt_unsaved_ ||
fl_choice("Discard unsaved project changes?", "Cancel", "Discard", nullptr) == 1;
}
/**
* @brief Hook used by the sequencer to seek the audio engine.
*
* Does nothing when the backend is disconnected. Otherwise reads the engine
* sample rate, converts the tick position to samples, and seeks the backend.
*
* @param context MainWindow * passed as the sequencer owner context.
* @param tick Target timeline position in project ticks.
* @return XTC_OK when disconnected or the seek succeeded, or the status,
* conversion, or backend seek error.
*/
xtc_result MainWindow::seek(void *context, uint64_t tick)
{
auto *window = static_cast<MainWindow *>(context);
@ -548,6 +707,15 @@ xtc_result MainWindow::seek(void *context, uint64_t tick)
return r == XTC_OK ? xtc_backend_seek(window->backend_, sample) : r;
}
/**
* @brief Saves the current project to a path and updates the window title.
*
* On success the path is remembered and the unsaved marker is cleared.
*
* @param path Destination project path; nullptr is invalid.
* @return XTC_OK on success, XTC_ERR_INVALID_ARGUMENT for nullptr,
* XTC_ERR_INVALID_STATE while recording, or the save error.
*/
xtc_result MainWindow::save_project(const char *path)
{
if (recorder_)
@ -564,6 +732,21 @@ xtc_result MainWindow::save_project(const char *path)
return r;
}
/**
* @brief Installs a candidate project as the active project.
*
* Rejected while recording. Optionally builds fresh undo history, stops and
* prepares the connected backend, or validates offline audio and MIDI plans
* when disconnected. On success the window takes ownership of candidate,
* rebinds widgets, resets the graph layout, clears the dirty flag, and shows
* any missing-asset warning. On failure ownership stays with the caller.
*
* @param candidate Project to install; must be non-null.
* @param reset_history True to discard existing undo/redo history and start
* from candidate.
* @return XTC_OK on success, XTC_ERR_INVALID_STATE while recording, or an
* error from history creation, the backend, or offline planning.
*/
xtc_result MainWindow::install_project(xtc_project *candidate, bool reset_history)
{
if (recorder_)
@ -625,6 +808,15 @@ xtc_result MainWindow::install_project(xtc_project *candidate, bool reset_histor
return XTC_OK;
}
/**
* @brief Loads a project from a path and makes it the current project.
*
* The file is loaded into a candidate project and installed only on success.
*
* @param path Source project path; nullptr is invalid.
* @return XTC_OK on success, XTC_ERR_INVALID_ARGUMENT for nullptr, or an
* error from loading or installing the project.
*/
xtc_result MainWindow::open_project(const char *path)
{
if (path == nullptr)
@ -645,6 +837,17 @@ xtc_result MainWindow::open_project(const char *path)
return r;
}
/**
* @brief FLTK menu callback for the File/New, Open, Save, and Save As items.
*
* Reads the action from the selected menu label, confirms discarding unsaved
* changes for New and Open, and uses a native file chooser unless Save can
* reuse the remembered path. Refuses file operations while recording and
* reports failures in the window.
*
* @param widget Triggering Fl_Menu_Bar whose selected label names the action.
* @param context MainWindow * owning the project and file path.
*/
void MainWindow::file_callback(Fl_Widget *widget, void *context)
{
auto *window = static_cast<MainWindow *>(context);
@ -694,6 +897,16 @@ void MainWindow::file_callback(Fl_Widget *widget, void *context)
window->report_error("Project operation failed", r);
}
/**
* @brief Hook called by arrangement widgets before mutating the project.
*
* Refuses edits while recording. When connected, stops the engine so the
* mutation cannot race the audio thread, then refreshes the status display.
*
* @param context MainWindow * passed as the widget owner context.
* @return XTC_OK when disconnected or the stop succeeded,
* XTC_ERR_INVALID_STATE while recording, or the backend stop error.
*/
xtc_result MainWindow::before_arrangement_edit(void *context)
{
auto *window = static_cast<MainWindow *>(context);
@ -706,6 +919,17 @@ xtc_result MainWindow::before_arrangement_edit(void *context)
return r;
}
/**
* @brief Imports a WAV file as an audio clip in a cloned project.
*
* Stops the engine first, creates an "Audio" track when none is selected,
* and places the clip at the sequencer cursor. The clone replaces the live
* project only after every step succeeds.
*
* @param path Path to the WAV file to import.
* @return XTC_OK on success, or an error from the engine stop, clone, import,
* or backend preparation.
*/
xtc_result MainWindow::import_audio(const char *path)
{
xtc_result r = before_arrangement_edit(this);
@ -732,6 +956,19 @@ xtc_result MainWindow::import_audio(const char *path)
return XTC_OK;
}
/**
* @brief Starts recording JACK input to a WAV file.
*
* Requires a connected backend and no active recording. The engine is
* stopped, the current position and selected track are remembered, and a
* stereo recorder at the engine sample rate is attached before starting.
* Failure leaves the window without a recorder.
*
* @param path Destination WAV path chosen by the user.
* @return XTC_OK when recording started, XTC_ERR_INVALID_STATE when
* disconnected or already recording, or another backend or recorder
* error.
*/
xtc_result MainWindow::record_audio(const char *path)
{
if (!ready_ || recorder_)
@ -763,6 +1000,14 @@ xtc_result MainWindow::record_audio(const char *path)
return r;
}
/**
* @brief Finalizes the active recording and adds the take to the project.
*
* Must run only after the engine has stopped, so no audio callback can
* reference the recorder ring. The recorder is detached, finished, and
* destroyed. A failed finalize or empty take is reported and not added. If
* the subsequent import fails, the saved WAV path is reported for recovery.
*/
void MainWindow::finish_recording()
{
// Called only after successful backend_stop: no producer can reference ring.
@ -807,6 +1052,18 @@ void MainWindow::finish_recording()
mark_dirty();
}
/**
* @brief FLTK callback for the audio import, record, and relink commands.
*
* The triggering widget supplies the menu label; the Record button uses the
* "Record WAV" default. A native file chooser selects the file, after which
* the selected clip is relinked, recording starts, or a WAV is imported.
* Failures are reported in the window.
*
* @param widget Triggering Fl_Menu_Bar or Fl_Button; menu labels select the
* action.
* @param context MainWindow * owning the project and sequencer.
*/
void MainWindow::audio_file_callback(Fl_Widget *widget, void *context)
{
auto *window = static_cast<MainWindow *>(context);
@ -843,6 +1100,14 @@ void MainWindow::audio_file_callback(Fl_Widget *widget, void *context)
window->report_error("Project audio operation failed", r);
}
/**
* @brief Shows the piano roll for the selected MIDI clip.
*
* The piano-roll window is created on first use and reused afterwards.
*
* @return XTC_OK on success, or XTC_ERR_INVALID_STATE when the selected clip
* does not exist or is not MIDI.
*/
xtc_result MainWindow::open_piano_roll()
{
xtc_clip_info clip{};
@ -873,6 +1138,14 @@ xtc_result MainWindow::open_piano_roll()
return XTC_OK;
}
/**
* @brief FLTK callback for the Piano roll menu item and button.
*
* Refuses to open the editor while recording and asks the user to select a
* MIDI clip when open_piano_roll() fails.
*
* @param context MainWindow * owning the piano roll.
*/
void MainWindow::piano_callback(Fl_Widget *, void *context)
{
auto *window = static_cast<MainWindow *>(context);
@ -885,6 +1158,14 @@ void MainWindow::piano_callback(Fl_Widget *, void *context)
set_label(window->error_, "Select a MIDI clip, then open Piano roll.");
}
/**
* @brief FLTK callback for the Panic menu item and button.
*
* When the backend is connected, sends a panic request that silences held
* instrument notes and reports failures.
*
* @param context MainWindow * owning the backend.
*/
void MainWindow::panic_callback(Fl_Widget *, void *context)
{
auto *window = static_cast<MainWindow *>(context);
@ -895,6 +1176,17 @@ void MainWindow::panic_callback(Fl_Widget *, void *context)
window->report_error("Panic failed", r);
}
/**
* @brief Applies one undo or redo step from the project history.
*
* The selected snapshot is installed without resetting history and then
* accepted, leaving the window marked dirty.
*
* @param redo True to redo, false to undo.
* @return XTC_OK on success, XTC_ERR_INVALID_STATE while recording,
* XTC_ERR_NOT_FOUND when no step is available, or another error from
* checkout, install, or accept.
*/
xtc_result MainWindow::undo(bool redo)
{
if (recorder_)
@ -913,6 +1205,15 @@ xtc_result MainWindow::undo(bool redo)
return r;
}
/**
* @brief FLTK menu callback for the Undo and Redo menu items.
*
* The selected menu label starting with "Redo" chooses redo; all other
* labels undo. Failures other than XTC_ERR_NOT_FOUND are shown in the window.
*
* @param widget Fl_Menu_Bar whose selected item triggered the callback.
* @param context MainWindow * owning the menu and history.
*/
void MainWindow::history_callback(Fl_Widget *widget, void *context)
{
auto *window = static_cast<MainWindow *>(context);

View file

@ -18,55 +18,262 @@ class Fl_Tabs;
class MainWindow : public Fl_Double_Window {
public:
/**
* @brief Constructs the window, its widgets, and the initial project.
*
* Creates an empty project and undo history, opens the audio backend, and
* schedules the periodic status timer. Throws std::runtime_error when the
* initial project or history cannot be created.
*
* @param prompt_unsaved Whether open, new, and close actions ask before
* discarding unsaved changes.
*/
explicit MainWindow(bool prompt_unsaved = true);
/**
* @brief Removes the status timer and releases backend and recording state.
*
* A backend that fails to close is intentionally retained with its recorder
* until process exit so active callbacks never observe freed state.
*/
~MainWindow() override;
/**
* @brief Deleted copy constructor; a window uniquely owns its project and backend.
*/
MainWindow(const MainWindow &) = delete;
/**
* @brief Deleted copy assignment; a window uniquely owns its project and backend.
*/
MainWindow &operator=(const MainWindow &) = delete;
using Fl_Double_Window::show;
/**
* @brief Handles FLTK events for the window.
*
* Space without Ctrl/Alt/Meta toggles the audio engine and is consumed.
*
* @param event FLTK event code, such as FL_SHORTCUT.
* @return 1 when Space was consumed; otherwise the Fl_Double_Window result.
*/
int handle(int event) override;
/**
* @brief Applies one undo or redo step from the project history.
*
* The selected snapshot is installed without resetting history and then
* accepted, leaving the window marked dirty.
*
* @param redo True to redo, false to undo.
* @return XTC_OK on success, XTC_ERR_INVALID_STATE while recording,
* XTC_ERR_NOT_FOUND when no step is available, or another error from
* checkout, install, or accept.
*/
xtc_result undo(bool redo = false);
/**
* @brief Returns the mixer tab widget owned by the window.
*
* @return Borrowed mixer widget, or nullptr before construction completes.
*/
MixerWidget *mixer() const
{
return mixer_;
}
/**
* @brief Shows the piano roll for the selected MIDI clip.
*
* The piano-roll window is created on first use and reused afterwards.
*
* @return XTC_OK on success, or XTC_ERR_INVALID_STATE when the selected clip
* does not exist or is not MIDI.
*/
xtc_result open_piano_roll();
/**
* @brief Returns the piano-roll editor widget.
*
* @return Borrowed widget, or nullptr until open_piano_roll() first succeeds.
*/
PianoRollWidget *piano_roll() const
{
return piano_;
}
/**
* @brief Imports a WAV file as an audio clip in a cloned project.
*
* Stops the engine first, creates an "Audio" track when none is selected,
* and places the clip at the sequencer cursor. The clone replaces the live
* project only after every step succeeds.
*
* @param path Path to the WAV file to import.
* @return XTC_OK on success, or an error from the engine stop, clone, import,
* or backend preparation.
*/
xtc_result import_audio(const char *path);
/**
* @brief Starts recording JACK input to a WAV file.
*
* Requires a connected backend and no active recording. The engine is
* stopped, the current position and selected track are remembered, and a
* stereo recorder at the engine sample rate is attached before starting.
* Failure leaves the window without a recorder.
*
* @param path Destination WAV path chosen by the user.
* @return XTC_OK when recording started, XTC_ERR_INVALID_STATE when
* disconnected or already recording, or another backend or recorder
* error.
*/
xtc_result record_audio(const char *path);
/**
* @brief Saves the current project to a path and updates the window title.
*
* On success the path is remembered and the unsaved marker is cleared.
*
* @param path Destination project path; nullptr is invalid.
* @return XTC_OK on success, XTC_ERR_INVALID_ARGUMENT for nullptr,
* XTC_ERR_INVALID_STATE while recording, or the save error.
*/
xtc_result save_project(const char *path);
/**
* @brief Loads a project from a path and makes it the current project.
*
* The file is loaded into a candidate project and installed only on success.
*
* @param path Source project path; nullptr is invalid.
* @return XTC_OK on success, XTC_ERR_INVALID_ARGUMENT for nullptr, or an
* error from loading or installing the project.
*/
xtc_result open_project(const char *path);
private:
MixerWidget *mixer_ = nullptr;
std::unique_ptr<xtc_history, decltype(&xtc_history_destroy)> history_{nullptr, xtc_history_destroy};
static void history_callback(Fl_Widget *, void *);
/**
* @brief FLTK menu callback for the Undo and Redo menu items.
*
* The selected menu label starting with "Redo" chooses redo; all other
* labels undo. Failures other than XTC_ERR_NOT_FOUND are shown in the window.
*
* @param widget Fl_Menu_Bar whose selected item triggered the callback.
* @param context MainWindow * owning the menu and history.
*/
static void history_callback(Fl_Widget * widget, void * context);
std::unique_ptr<xtc_project, decltype(&xtc_project_destroy)> project_{nullptr, xtc_project_destroy};
std::unique_ptr<Fl_Double_Window> piano_window_;
PianoRollWidget *piano_ = nullptr;
static void piano_callback(Fl_Widget *, void *context);
static void panic_callback(Fl_Widget *, void *context);
/**
* @brief FLTK callback for the Piano roll menu item and button.
*
* Refuses to open the editor while recording and asks the user to select a
* MIDI clip when open_piano_roll() fails.
*
* @param widget Unused triggering widget.
* @param context MainWindow * owning the piano roll.
*/
static void piano_callback(Fl_Widget * widget, void *context);
/**
* @brief FLTK callback for the Panic menu item and button.
*
* When the backend is connected, sends a panic request that silences held
* instrument notes and reports failures.
*
* @param widget Unused triggering widget.
* @param context MainWindow * owning the backend.
*/
static void panic_callback(Fl_Widget * widget, void *context);
SequencerWidget *sequencer_ = nullptr;
xtc_recorder *recorder_ = nullptr;
std::string recording_path_;
uint64_t recording_tick_ = 0, recording_track_ = UINT64_MAX;
/**
* @brief Finalizes the active recording and adds the take to the project.
*
* Must run only after the engine has stopped, so no audio callback can
* reference the recorder ring. The recorder is detached, finished, and
* destroyed. A failed finalize or empty take is reported and not added. If
* the subsequent import fails, the saved WAV path is reported for recovery.
*/
void finish_recording();
/**
* @brief Hook called by arrangement widgets before mutating the project.
*
* Refuses edits while recording. When connected, stops the engine so the
* mutation cannot race the audio thread, then refreshes the status display.
*
* @param context MainWindow * passed as the widget owner context.
* @return XTC_OK when disconnected or the stop succeeded,
* XTC_ERR_INVALID_STATE while recording, or the backend stop error.
*/
static xtc_result before_arrangement_edit(void *context);
static void audio_file_callback(Fl_Widget *, void *context);
/**
* @brief FLTK callback for the audio import, record, and relink commands.
*
* The triggering widget supplies the menu label; the Record button uses the
* "Record WAV" default. A native file chooser selects the file, after which
* the selected clip is relinked, recording starts, or a WAV is imported.
* Failures are reported in the window.
*
* @param widget Triggering Fl_Menu_Bar or Fl_Button; menu labels select the
* action.
* @param context MainWindow * owning the project and sequencer.
*/
static void audio_file_callback(Fl_Widget * widget, void *context);
bool dirty_ = false, prompt_unsaved_ = true;
std::string path_;
/**
* @brief Asks whether unsaved changes may be discarded.
*
* @return True when the project is clean, prompting is disabled, or the user
* chose Discard; false when the user cancels.
*/
bool discard_changes();
/**
* @brief Records the current project in history and marks the window dirty.
*
* Dependent widgets are refreshed with the current project and graph, and
* the title gains the unsaved marker. A history failure is reported.
*/
void mark_dirty();
/**
* @brief Installs a candidate project as the active project.
*
* Rejected while recording. Optionally builds fresh undo history, stops and
* prepares the connected backend, or validates offline audio and MIDI plans
* when disconnected. On success the window takes ownership of candidate,
* rebinds widgets, resets the graph layout, clears the dirty flag, and shows
* any missing-asset warning. On failure ownership stays with the caller.
*
* @param candidate Project to install; must be non-null.
* @param reset_history True to discard existing undo/redo history and start
* from candidate.
* @return XTC_OK on success, XTC_ERR_INVALID_STATE while recording, or an
* error from history creation, the backend, or offline planning.
*/
xtc_result install_project(xtc_project *candidate, bool reset_history = true);
/**
* @brief Hook used by the sequencer to seek the audio engine.
*
* Does nothing when the backend is disconnected. Otherwise reads the engine
* sample rate, converts the tick position to samples, and seeks the backend.
*
* @param context MainWindow * passed as the sequencer owner context.
* @param tick Target timeline position in project ticks.
* @return XTC_OK when disconnected or the seek succeeded, or the status,
* conversion, or backend seek error.
*/
static xtc_result seek(void *context, uint64_t tick);
static void file_callback(Fl_Widget *, void *context);
/**
* @brief FLTK menu callback for the File/New, Open, Save, and Save As items.
*
* Reads the action from the selected menu label, confirms discarding unsaved
* changes for New and Open, and uses a native file chooser unless Save can
* reuse the remembered path. Refuses file operations while recording and
* reports failures in the window.
*
* @param widget Triggering Fl_Menu_Bar whose selected label names the action.
* @param context MainWindow * owning the project and file path.
*/
static void file_callback(Fl_Widget * widget, void *context);
GraphWidget *graph_widget_ = nullptr;
Fl_Box *graph_state_ = nullptr;
xtc_backend *backend_ = nullptr;
@ -81,20 +288,129 @@ class MainWindow : public Fl_Double_Window {
Fl_Box *error_ = nullptr;
Fl_Tabs *tabs_ = nullptr;
/**
* @brief Edit handler passed to the modular graph widget.
*
* Applies the edit to a candidate graph, clones the project around it, and
* submits the new graph to the engine when connected. The live project is
* replaced only after every step succeeds.
*
* @param context MainWindow * passed as the graph widget owner context.
* @param edit Graph edit to apply.
* @param out_added_id Optional output receiving the new node ID when the edit
* adds a node; untouched otherwise or when nullptr.
* @return XTC_OK on success, XTC_ERR_INVALID_STATE while recording, or an
* error from the graph, project, or backend operation.
*/
static xtc_result edit_graph(void *context, const xtc_graph_edit &edit, size_t *out_added_id);
/**
* @brief Opens the JACK backend and submits the current graph.
*
* Uses the default backend configuration and leaves the engine stopped.
* Failure is reported and leaves the backend disconnected; a handle that
* opened but failed to accept the graph is retained for reconnect.
*/
void open_backend();
/**
* @brief Starts the audio engine with the current project.
*
* Does nothing when already running. Reports an error when disconnected.
* Otherwise prepares the project and starts the engine. Missing or
* unreadable audio is reported as a warning after a successful start.
*/
void start_engine();
/**
* @brief Stops the audio engine, finalizing any active recording.
*
* Does nothing when disconnected. Failures are reported in the window.
*/
void stop_engine();
/**
* @brief Closes and reopens the JACK backend, leaving the engine stopped.
*
* Refuses to run while recording. If closing fails the existing handle is
* retained for a later retry and the failure is reported.
*/
void reconnect_engine();
/**
* @brief Polls the backend and updates labels, meters, and button states.
*
* When disconnected, shows offline text and disables transport controls.
* Otherwise updates the sequencer position, mixer peaks, status line, and
* error indicators from the latest engine status, then collects backend
* work.
*/
void refresh_status();
/**
* @brief Attempts to close the window after discarding unsaved changes.
*
* Refuses while recording. On success closes the backend, removes the status
* timer, hides the piano-roll window, and hides this window.
*
* @return True when the window closed; false when recording is active, the
* user cancels discarding, or the backend cannot close.
*/
bool close_window();
/**
* @brief Shows an operation failure and a context-sensitive hint.
*
* @param operation Human-readable operation name; names containing
* "Project" select a file and permission hint.
* @param result Failing result code whose name is included in the message.
*/
void report_error(const char *operation, xtc_result result);
static void play_callback(Fl_Widget *, void *context);
static void stop_callback(Fl_Widget *, void *context);
static void reconnect_callback(Fl_Widget *, void *context);
static void close_callback(Fl_Widget *, void *context);
/**
* @brief FLTK callback for the Play menu item and button.
*
* @param widget Unused triggering widget.
* @param context MainWindow * whose engine is started.
*/
static void play_callback(Fl_Widget * widget, void *context);
/**
* @brief FLTK callback for the Stop menu item and button.
*
* @param widget Unused triggering widget.
* @param context MainWindow * whose engine is stopped.
*/
static void stop_callback(Fl_Widget * widget, void *context);
/**
* @brief FLTK callback for the Reconnect menu item and button.
*
* @param widget Unused triggering widget.
* @param context MainWindow * whose backend is reopened.
*/
static void reconnect_callback(Fl_Widget * widget, void *context);
/**
* @brief FLTK callback for the window close box, File/Quit, and close events.
*
* @param widget Unused triggering widget or window.
* @param context MainWindow * whose close is requested; a refused close
* simply leaves the window open.
*/
static void close_callback(Fl_Widget * widget, void *context);
/**
* @brief FLTK menu callback that selects a tab page in the main window.
*
* @param widget Triggering menu item; window() must return the MainWindow.
* @param view Fl_Widget * tab page to make current, not the MainWindow.
*/
static void view_callback(Fl_Widget *widget, void *view);
static void about_callback(Fl_Widget *, void *);
/**
* @brief FLTK help callback that shows the application name and version.
*
* @param widget Unused triggering widget.
* @param context Unused callback context.
*/
static void about_callback(Fl_Widget * widget, void * context);
/**
* @brief FLTK timeout callback that refreshes status and supervises recording.
*
* While recording, drains the recorder and stops the engine if draining
* fails or the backend reports a lost server, sample-rate change, or
* processing error. Reschedules itself every 0.1 seconds until closing.
*
* @param context MainWindow * registered with Fl::add_timeout().
*/
static void timer_callback(void *context);
};

View file

@ -22,10 +22,25 @@ class AutomationLane : public Fl_Widget {
MixerWidget &mixer_;
public:
/**
* @brief Creates an automation lane bound to its owning mixer.
*
* @param x Left edge in parent coordinates.
* @param y Top edge in parent coordinates.
* @param w Lane width in pixels; tick math requires a value greater than 1.
* @param h Lane height in pixels.
* @param m Mixer that owns the lane; stored by reference and must outlive it.
*/
AutomationLane(int x, int y, int w, int h, MixerWidget &m) : Fl_Widget(x, y, w, h), mixer_(m)
{
}
/**
* @brief Draws the lane grid, automation curve, points, and empty hint.
*
* Points for the selected track and parameter are sorted by tick, rendered
* as an interpolated curve, and marked within the visible tick range.
*/
void draw() override
{
fl_push_clip(x(), y(), w(), h());
@ -100,6 +115,12 @@ class AutomationLane : public Fl_Widget {
fl_pop_clip();
}
/**
* @brief Handles FLTK input events and delegates unhandled events to the base widget.
*
* @param event FLTK event code.
* @return Nonzero if the event was handled, zero otherwise.
*/
int handle(int event) override
{
if (event != FL_PUSH)
@ -136,6 +157,20 @@ class AutomationLane : public Fl_Widget {
}
};
/**
* @brief Creates the mixer panel and its child controls.
*
* Builds the track chooser, gain/pan sliders, mute/solo buttons, output
* meters, automation controls, and lane, then displays the given project.
*
* @param x Left edge in parent coordinates.
* @param y Top edge in parent coordinates.
* @param w Widget width in pixels.
* @param h Widget height in pixels.
* @param p Project to display; borrowed and must outlive its use.
* @param before Callback invoked before each edit; may be nullptr.
* @param context Opaque pointer forwarded to before.
*/
MixerWidget::MixerWidget(int x, int y, int w, int h, xtc_project *p, BeforeEdit before, void *context)
: Fl_Group(x, y, w, h), project_(p), before_(before), context_(context)
{
@ -182,17 +217,35 @@ MixerWidget::MixerWidget(int x, int y, int w, int h, xtc_project *p, BeforeEdit
set_project(p);
}
/**
* @brief Returns the ID of the track selected in the track chooser.
*
* @return Selected track ID, or UINT64_MAX when the chooser selection does
* not resolve to a project track.
*/
uint64_t MixerWidget::selected_track() const
{
xtc_track_info t{};
return xtc_project_get_track(project_, static_cast<size_t>(tracks_->value()), &t) == XTC_OK ? t.id : UINT64_MAX;
}
/**
* @brief Returns the automation parameter selected in the chooser.
*
* @return XTC_AUTOMATION_PAN when the chooser selects "Pan", otherwise
* XTC_AUTOMATION_GAIN.
*/
xtc_automation_parameter MixerWidget::parameter() const
{
return parameter_->value() == 1 ? XTC_AUTOMATION_PAN : XTC_AUTOMATION_GAIN;
}
/**
* @brief Returns the first tick shown by the automation lane.
*
* @return Clamped first tick in project ticks, or 0 when the input value is
* not finite.
*/
uint64_t MixerWidget::lane_start() const
{
if (!std::isfinite(start_->value()))
@ -201,6 +254,14 @@ uint64_t MixerWidget::lane_start() const
std::clamp(start_->value(), 0.0, static_cast<double>(XTC_PROJECT_MAX_TICK - lane_ticks)));
}
/**
* @brief Displays a different project and refreshes the controls.
*
* The current track selection is preserved only when the same project
* pointer is passed again. The project is borrowed and must outlive use.
*
* @param p Project to display; must remain valid while shown.
*/
void MixerWidget::set_project(xtc_project *p)
{
uint64_t selected = project_ == p ? selected_track() : UINT64_MAX;
@ -227,6 +288,13 @@ void MixerWidget::set_project(xtc_project *p)
redraw();
}
/**
* @brief Synchronizes the child controls with the selected track.
*
* Controls are deactivated when no valid track is selected. Gain falls
* back to 1, and the remaining values come from the track info or zero when
* unresolved.
*/
void MixerWidget::selection()
{
xtc_track_info t{};
@ -246,6 +314,15 @@ void MixerWidget::selection()
lane_->redraw();
}
/**
* @brief Updates the left and right output meters.
*
* Meter values are clamped to 1.0, while the over-range color is chosen
* from the unclamped level.
*
* @param left Left-channel peak level.
* @param right Right-channel peak level.
*/
void MixerWidget::peaks(float left, float right)
{
left_->value(std::min(left, 1.0f));
@ -254,6 +331,16 @@ void MixerWidget::peaks(float left, float right)
right_->selection_color(right > 1 ? FL_RED : FL_GREEN);
}
/**
* @brief Reports an edit result and refreshes the controls.
*
* A successful result invokes the widget callback so the owner can mark
* the project dirty; a failure shows a modal alert. The controls are
* resynchronized in both cases.
*
* @param r Result of the attempted edit.
* @return The same result passed in.
*/
xtc_result MixerWidget::changed(xtc_result r)
{
if (r == XTC_OK)
@ -264,6 +351,19 @@ xtc_result MixerWidget::changed(xtc_result r)
return r;
}
/**
* @brief Applies gain, pan, mute, and solo to the selected track.
*
* The constructor's before-edit callback runs first; the edit is skipped
* if it returns an error. On success the widget callback is invoked and
* the controls are refreshed.
*
* @param gain Linear gain in the range 0..4.
* @param pan Pan position in the range -1 (left)..1 (right).
* @param mute Mute state: 0 or 1.
* @param solo Solo state: 0 or 1.
* @return XTC_OK on success, or the before-edit or project mutation error.
*/
xtc_result MixerWidget::mix(float gain, float pan, int mute, int solo)
{
xtc_result r = before_ ? before_(context_) : XTC_OK;
@ -272,6 +372,18 @@ xtc_result MixerWidget::mix(float gain, float pan, int mute, int solo)
return changed(r);
}
/**
* @brief Adds, replaces, or removes an automation point.
*
* The constructor's before-edit callback runs first; the edit is skipped
* if it returns an error. On success the widget callback is invoked and
* the controls are refreshed.
*
* @param tick Automation tick to modify; must be within the project range.
* @param value New value; ignored when remove is true.
* @param remove When true, removes the point instead of setting it.
* @return XTC_OK on success, or the before-edit or project mutation error.
*/
xtc_result MixerWidget::point(uint64_t tick, float value, bool remove)
{
xtc_result r = before_ ? before_(context_) : XTC_OK;
@ -282,6 +394,14 @@ xtc_result MixerWidget::point(uint64_t tick, float value, bool remove)
return changed(r);
}
/**
* @brief FLTK callback for the gain, pan, mute, and solo controls.
*
* Reads all four controls and applies them together to the selected track
* through mix().
*
* @param c Opaque MixerWidget pointer supplied as callback context.
*/
void MixerWidget::control(Fl_Widget *, void *c)
{
auto *m = static_cast<MixerWidget *>(c);
@ -289,11 +409,25 @@ void MixerWidget::control(Fl_Widget *, void *c)
m->solo_->value());
}
/**
* @brief FLTK callback for the track, parameter, and first-tick controls.
*
* @param c Opaque MixerWidget pointer supplied as callback context.
*/
void MixerWidget::select(Fl_Widget *, void *c)
{
static_cast<MixerWidget *>(c)->selection();
}
/**
* @brief Handles keyboard shortcuts and forwards other events.
*
* M and S toggle mute and solo, Left/Right adjust gain (Shift adjusts pan),
* and Up/Down change the selected track. Modified key presses fall through.
*
* @param e FLTK event code.
* @return 1 when the event was consumed, otherwise the Fl_Group result.
*/
int MixerWidget::handle(int e)
{
if (e == FL_KEYDOWN && !(Fl::event_state() & (FL_CTRL | FL_ALT | FL_META))) {

View file

@ -12,22 +12,124 @@ class AutomationLane;
class MixerWidget : public Fl_Group {
public:
/**
* @brief Prepares a model edit using the widget's opaque callback context.
*
* @return XTC_OK to allow the edit, or an error to cancel it.
*/
using BeforeEdit = xtc_result (*)(void *);
MixerWidget(int, int, int, int, xtc_project *, BeforeEdit, void *);
void set_project(xtc_project *);
/**
* @brief Creates the mixer panel and its child controls.
*
* Builds the track chooser, gain/pan sliders, mute/solo buttons, output
* meters, automation controls, and lane, then displays the given project.
*
* @param x Left edge in parent coordinates.
* @param y Top edge in parent coordinates.
* @param w Widget width in pixels.
* @param h Widget height in pixels.
* @param p Project to display; borrowed and must outlive its use.
* @param before Callback invoked before each edit; may be nullptr.
* @param context Opaque pointer forwarded to before.
*/
MixerWidget(int x, int y, int w, int h, xtc_project * p, BeforeEdit before, void * context);
/**
* @brief Displays a different project and refreshes the controls.
*
* The current track selection is preserved only when the same project
* pointer is passed again. The project is borrowed and must outlive use.
*
* @param p Project to display; must remain valid while shown.
*/
void set_project(xtc_project * p);
/**
* @brief Updates the left and right output meters.
*
* Meter values are clamped to 1.0, while the over-range color is chosen
* from the unclamped level.
*
* @param left Left-channel peak level.
* @param right Right-channel peak level.
*/
void peaks(float left, float right);
/**
* @brief Returns the ID of the track selected in the track chooser.
*
* @return Selected track ID, or UINT64_MAX when the chooser selection does
* not resolve to a project track.
*/
uint64_t selected_track() const;
/**
* @brief Returns the project currently displayed by the widget.
*
* @return Borrowed project pointer, possibly nullptr; the widget does not
* own it.
*/
xtc_project *project() const
{
return project_;
}
/**
* @brief Returns the automation parameter selected in the chooser.
*
* @return XTC_AUTOMATION_PAN when the chooser selects "Pan", otherwise
* XTC_AUTOMATION_GAIN.
*/
xtc_automation_parameter parameter() const;
/**
* @brief Returns the first tick shown by the automation lane.
*
* @return Clamped first tick in project ticks, or 0 when the input value is
* not finite.
*/
uint64_t lane_start() const;
/**
* @brief Adds, replaces, or removes an automation point.
*
* The constructor's before-edit callback runs first; the edit is skipped
* if it returns an error. On success the widget callback is invoked and
* the controls are refreshed.
*
* @param tick Automation tick to modify; must be within the project range.
* @param value New value; ignored when remove is true.
* @param remove When true, removes the point instead of setting it.
* @return XTC_OK on success, or the before-edit or project mutation error.
*/
xtc_result point(uint64_t tick, float value, bool remove = false);
/**
* @brief Applies gain, pan, mute, and solo to the selected track.
*
* The constructor's before-edit callback runs first; the edit is skipped
* if it returns an error. On success the widget callback is invoked and
* the controls are refreshed.
*
* @param gain Linear gain in the range 0..4.
* @param pan Pan position in the range -1 (left)..1 (right).
* @param mute Mute state: 0 or 1.
* @param solo Solo state: 0 or 1.
* @return XTC_OK on success, or the before-edit or project mutation error.
*/
xtc_result mix(float gain, float pan, int mute, int solo);
int handle(int) override;
/**
* @brief Handles keyboard shortcuts and forwards other events.
*
* M and S toggle mute and solo, Left/Right adjust gain (Shift adjusts pan),
* and Up/Down change the selected track. Modified key presses fall through.
*
* @param event FLTK event code.
* @return 1 when the event was consumed, otherwise the Fl_Group result.
*/
int handle(int event) override;
private:
xtc_project *project_;
@ -39,9 +141,44 @@ class MixerWidget : public Fl_Group {
Fl_Progress *left_, *right_;
Fl_Value_Input *start_;
AutomationLane *lane_;
xtc_result changed(xtc_result);
/**
* @brief Reports an edit result and refreshes the controls.
*
* A successful result invokes the widget callback so the owner can mark
* the project dirty; a failure shows a modal alert. The controls are
* resynchronized in both cases.
*
* @param r Result of the attempted edit.
* @return The same result passed in.
*/
xtc_result changed(xtc_result r);
/**
* @brief Synchronizes the child controls with the selected track.
*
* Controls are deactivated when no valid track is selected. Gain falls
* back to 1, and the remaining values come from the track info or zero when
* unresolved.
*/
void selection();
static void control(Fl_Widget *, void *);
static void select(Fl_Widget *, void *);
/**
* @brief FLTK callback for the gain, pan, mute, and solo controls.
*
* Reads all four controls and applies them together to the selected track
* through mix().
*
* @param widget Widget that triggered the callback; unused.
* @param c Opaque MixerWidget pointer supplied as callback context.
*/
static void control(Fl_Widget * widget, void * c);
/**
* @brief FLTK callback for the track, parameter, and first-tick controls.
*
* @param widget Widget that triggered the callback; unused.
* @param c Opaque MixerWidget pointer supplied as callback context.
*/
static void select(Fl_Widget * widget, void * c);
};
#endif

View file

@ -9,6 +9,19 @@ namespace {
constexpr int key_width = 64, row_height = 16, scale = 96, header = 28;
}
/**
* @brief Creates a piano-roll editor for a MIDI clip.
*
* The pre-edit guard, when non-NULL, is called before every project mutation
* and can veto the edit by returning an error.
*
* @param left Left edge of the widget in the parent's coordinate system.
* @param top Top edge of the widget in the parent's coordinate system.
* @param project Borrowed project that must outlive the widget.
* @param clip ID of the MIDI clip to edit.
* @param before Optional pre-edit guard; NULL means no guard is used.
* @param context Opaque pointer passed to @p before on every edit.
*/
PianoRollWidget::PianoRollWidget(int left, int top, xtc_project *project, uint64_t clip, BeforeEdit before,
void *context)
: Fl_Widget(left, top, 900, header + 128 * row_height), project_(project), clip_(clip), before_(before),
@ -17,6 +30,15 @@ PianoRollWidget::PianoRollWidget(int left, int top, xtc_project *project, uint64
set_project(project, clip);
}
/**
* @brief Points the editor at a different project or clip.
*
* Clears any in-progress drag when the project or clip changes and resizes the
* widget to fit the clip's source length. The current selection is not changed.
*
* @param project Borrowed project that must outlive the widget.
* @param clip ID of the MIDI clip to display.
*/
void PianoRollWidget::set_project(xtc_project *project, uint64_t clip)
{
if (project != project_ || clip != clip_)
@ -31,11 +53,30 @@ void PianoRollWidget::set_project(xtc_project *project, uint64_t clip)
redraw();
}
/**
* @brief Converts a horizontal pixel position to a snapped tick.
*
* The position is measured in absolute window coordinates and snapped down to
* the nearest sixteenth note (240 ticks).
*
* @param px Horizontal position in window coordinates.
* @return Tick offset within the clip source, or 0 for positions on or left of
* the keyboard.
*/
uint64_t PianoRollWidget::tick(int px) const
{
return static_cast<uint64_t>(std::max(0, px - x() - key_width)) * 960U / scale / 240U * 240U;
}
/**
* @brief Reports an edit result and notifies listeners on success.
*
* On success the widget callback runs and the editor is redrawn; on failure an
* alert shows the error and the project is left unchanged.
*
* @param result Result of the attempted project mutation.
* @return The same result code.
*/
xtc_result PianoRollWidget::change(xtc_result result)
{
if (result == XTC_OK) {
@ -46,6 +87,18 @@ xtc_result PianoRollWidget::change(xtc_result result)
return result;
}
/**
* @brief Adds a note to the current clip and selects the new note.
*
* Invokes the pre-edit guard, if any, before mutating the project.
*
* @param start Note start in clip source ticks.
* @param length Note length in ticks.
* @param pitch MIDI pitch from 0 to 127.
* @param velocity MIDI velocity from 1 to 127.
* @param channel MIDI channel from 0 to 15.
* @return XTC_OK on success, the guard's error, or the project error.
*/
xtc_result PianoRollWidget::add_note(uint64_t start, uint64_t length, unsigned int pitch, unsigned int velocity,
unsigned int channel)
{
@ -58,6 +111,14 @@ xtc_result PianoRollWidget::add_note(uint64_t start, uint64_t length, unsigned i
return change(r);
}
/**
* @brief Removes the selected note from the current clip.
*
* Invokes the pre-edit guard, if any, before mutating the project. The
* selection is cleared only when the removal succeeds.
*
* @return XTC_OK on success, the guard's error, or the project error.
*/
xtc_result PianoRollWidget::delete_selected()
{
xtc_result r = before_ ? before_(context_) : XTC_OK;
@ -68,6 +129,11 @@ xtc_result PianoRollWidget::delete_selected()
return change(r);
}
/**
* @brief Draws the keyboard, beat grid, and notes of the current clip.
*
* Paints only the background when the clip is missing or is not a MIDI clip.
*/
void PianoRollWidget::draw()
{
fl_push_clip(x(), y(), w(), h());
@ -124,6 +190,17 @@ void PianoRollWidget::draw()
fl_pop_clip();
}
/**
* @brief Handles mouse, keyboard, and focus events for the editor.
*
* Left-click on empty space adds a note, clicking a note selects it, and
* dragging moves or resizes it. Right-click and Delete remove the selected
* note; Escape cancels an in-progress drag.
*
* @param event FLTK event code.
* @return 1 when the event was handled, otherwise the result of the base
* class handler.
*/
int PianoRollWidget::handle(int event)
{
if (event == FL_PUSH) {

View file

@ -5,21 +5,90 @@
class PianoRollWidget : public Fl_Widget {
public:
/**
* @brief Prepares a model edit using the widget's opaque callback context.
*
* @return XTC_OK to allow the edit, or an error to cancel it.
*/
using BeforeEdit = xtc_result (*)(void *);
/**
* @brief Creates a piano-roll editor for a MIDI clip.
*
* The pre-edit guard, when non-NULL, is called before every project
* mutation and can veto the edit by returning an error.
*
* @param x Left edge of the widget in the parent's coordinate system.
* @param y Top edge of the widget in the parent's coordinate system.
* @param project Borrowed project that must outlive the widget.
* @param clip ID of the MIDI clip to edit.
* @param before Optional pre-edit guard; NULL means no guard is used.
* @param context Opaque pointer passed to @p before on every edit.
*/
PianoRollWidget(int x, int y, xtc_project *project, uint64_t clip, BeforeEdit before, void *context);
/**
* @brief Points the editor at a different project or clip.
*
* Clears any in-progress drag when the project or clip changes and resizes
* the widget to fit the clip's source length. The current selection is not
* changed.
*
* @param project Borrowed project that must outlive the widget.
* @param clip ID of the MIDI clip to display.
*/
void set_project(xtc_project *project, uint64_t clip);
/**
* @brief Returns the ID of the clip currently being edited.
*
* @return Clip identifier supplied to the constructor or set_project().
*/
uint64_t clip() const
{
return clip_;
}
/**
* @brief Adds a note to the current clip and selects the new note.
*
* Invokes the pre-edit guard, if any, before mutating the project.
*
* @param start Note start in clip source ticks.
* @param length Note length in ticks.
* @param pitch MIDI pitch from 0 to 127.
* @param velocity MIDI velocity from 1 to 127.
* @param channel MIDI channel from 0 to 15.
* @return XTC_OK on success, the guard's error, or the project error.
*/
xtc_result add_note(uint64_t start, uint64_t length, unsigned int pitch, unsigned int velocity = 100,
unsigned int channel = 0);
/**
* @brief Removes the selected note from the current clip.
*
* Invokes the pre-edit guard, if any, before mutating the project. The
* selection is cleared only when the removal succeeds.
*
* @return XTC_OK on success, the guard's error, or the project error.
*/
xtc_result delete_selected();
/**
* @brief Handles mouse, keyboard, and focus events for the editor.
*
* Left-click on empty space adds a note, clicking a note selects it, and
* dragging moves or resizes it. Right-click and Delete remove the selected
* note; Escape cancels an in-progress drag.
*
* @param event FLTK event code.
* @return 1 when the event was handled, otherwise the result of the base
* class handler.
*/
int handle(int event) override;
protected:
/**
* @brief Draws the keyboard, beat grid, and notes of the current clip.
*
* Paints only the background when the clip is missing or is not a MIDI clip.
*/
void draw() override;
private:
@ -29,7 +98,26 @@ class PianoRollWidget : public Fl_Widget {
void *context_;
xtc_note_info original_{};
int mode_ = 0, drag_x_ = 0, drag_y_ = 0;
/**
* @brief Reports an edit result and notifies listeners on success.
*
* On success the widget callback runs and the editor is redrawn; on failure
* an alert shows the error and the project is left unchanged.
*
* @param result Result of the attempted project mutation.
* @return The same result code.
*/
xtc_result change(xtc_result result);
/**
* @brief Converts a horizontal pixel position to a snapped tick.
*
* The position is measured in absolute window coordinates and snapped down
* to the nearest sixteenth note (240 ticks).
*
* @param px Horizontal position in window coordinates.
* @return Tick offset within the clip source, or 0 for positions on or left
* of the keyboard.
*/
uint64_t tick(int px) const;
};
#endif

View file

@ -7,12 +7,27 @@
#include <algorithm>
#include <cstdio>
/**
* @brief Constructs the timeline widget with a borrowed project and seek callback.
*
* @param left Left coordinate in pixels.
* @param top Top coordinate in pixels.
* @param project Borrowed project; must outlive the widget's use of it.
* @param seek Callback used to request a transport seek.
* @param context Opaque callback context.
*/
SequencerWidget::SequencerWidget(int left, int top, xtc_project *project, Seek seek, void *context)
: Fl_Widget(left, top, 6300, 3700), project_(project), seek_(seek), context_(context)
{
extent();
}
/**
* @brief Replaces the borrowed project and refreshes the widget's displayed state.
*
* @param project Borrowed project; must outlive the widget's use of it.
* @param reset Whether to clear selection, cursor, and drag state.
*/
void SequencerWidget::set_project(xtc_project *project, bool reset)
{
project_ = project;
@ -25,12 +40,21 @@ void SequencerWidget::set_project(xtc_project *project, bool reset)
redraw();
}
/**
* @brief Converts a screen x coordinate to a bounded timeline tick snapped to a sixteenth note.
*
* @param px Screen x coordinate in pixels.
* @return Snapped tick within the supported project range.
*/
uint64_t SequencerWidget::tick(int px) const
{
return std::min(XTC_PROJECT_MAX_TICK, static_cast<uint64_t>(std::max(0, px - x() - 120)) * 960U /
static_cast<unsigned int>(scale_) / 240U * 240U);
}
/**
* @brief Resizes the timeline's scrollable area to fit its tracks and clip ends.
*/
void SequencerWidget::extent()
{
size_t tracks, clips;
@ -47,6 +71,11 @@ void SequencerWidget::extent()
parent()->redraw();
}
/**
* @brief Reports an edit result, refreshes the widget, and notifies listeners on success.
*
* @param result Result of the attempted operation.
*/
void SequencerWidget::changed(xtc_result result)
{
if (result != XTC_OK)
@ -58,6 +87,10 @@ void SequencerWidget::changed(xtc_result result)
redraw();
}
/**
* @brief Creates and selects a track through the arrangement edit hook.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result SequencerWidget::add_track()
{
if (before_edit) {
@ -77,6 +110,10 @@ xtc_result SequencerWidget::add_track()
return r;
}
/**
* @brief Creates and selects a one-bar silent region at the timeline cursor.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result SequencerWidget::add_region()
{
if (before_edit) {
@ -101,6 +138,10 @@ xtc_result SequencerWidget::add_region()
return r;
}
/**
* @brief Creates and selects a one-bar MIDI clip at the timeline cursor.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result SequencerWidget::add_midi_clip()
{
xtc_result r = before_edit ? before_edit(context_) : XTC_OK;
@ -120,6 +161,10 @@ xtc_result SequencerWidget::add_midi_clip()
return r;
}
/**
* @brief Removes the selected clip, or the selected track when no clip is selected.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result SequencerWidget::delete_selection()
{
if (before_edit) {
@ -137,6 +182,11 @@ xtc_result SequencerWidget::delete_selection()
return r;
}
/**
* @brief Adjusts timeline zoom within its supported bounds and updates the scroll extent.
*
* @param direction Zoom direction; positive zooms in, nonpositive zooms out.
*/
void SequencerWidget::zoom(int direction)
{
scale_ = std::clamp(direction > 0 ? scale_ * 2 : scale_ / 2, 24, 384);
@ -144,6 +194,9 @@ void SequencerWidget::zoom(int direction)
redraw();
}
/**
* @brief Draws the widget's current model, selection, and interaction feedback.
*/
void SequencerWidget::draw()
{
fl_push_clip(x(), y(), w(), h());
@ -203,6 +256,12 @@ void SequencerWidget::draw()
fl_pop_clip();
}
/**
* @brief Handles FLTK input events and delegates unhandled events to the base widget.
*
* @param event FLTK event code.
* @return Nonzero if the event was handled, zero otherwise.
*/
int SequencerWidget::handle(int event)
{
if (event == FL_PUSH && Fl::event_button() == FL_LEFT_MOUSE) {

View file

@ -5,48 +5,124 @@
class SequencerWidget : public Fl_Widget {
public:
/**
* @brief Prepares a model edit using the widget's opaque callback context.
*
* @return XTC_OK to allow the edit, or an error to cancel it.
*/
using BeforeEdit = xtc_result (*)(void *);
BeforeEdit before_edit = nullptr;
/**
* @brief Returns the selected track identifier, or UINT64_MAX when none is selected.
* @return Stable track identifier, or UINT64_MAX if no track is selected.
*/
uint64_t selected_track() const
{
return track_;
}
/**
* @brief Returns the selected clip identifier, or UINT64_MAX when none is selected.
* @return Stable clip identifier, or UINT64_MAX if no clip is selected.
*/
uint64_t selected_clip() const
{
return clip_;
}
/**
* @brief Requests a transport seek using an opaque context and musical tick.
*
* @return XTC_OK on success, or a transport error.
*/
using Seek = xtc_result (*)(void *, uint64_t);
/**
* @brief Constructs the timeline widget with a borrowed project and seek callback.
*
* @param x Left coordinate in pixels.
* @param y Top coordinate in pixels.
* @param project Borrowed project; must outlive the widget's use of it.
* @param seek Callback used to request a transport seek.
* @param context Opaque callback context.
*/
SequencerWidget(int x, int y, xtc_project *project, Seek seek, void *context);
/**
* @brief Replaces the borrowed project and refreshes the widget's displayed state.
*
* @param project Borrowed project; must outlive the widget's use of it.
* @param reset Whether to clear selection, cursor, and drag state.
*/
void set_project(xtc_project *project, bool reset = true);
/**
* @brief Returns the timeline cursor position in musical ticks.
* @return Cursor position in musical ticks.
*/
uint64_t position() const
{
return cursor_;
}
/**
* @brief Moves the timeline cursor to the supplied musical tick and requests a redraw.
*
* @param tick Musical position in ticks.
*/
void position(uint64_t tick)
{
cursor_ = tick;
redraw();
}
/**
* @brief Creates and selects a track through the arrangement edit hook.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result add_track();
/**
* @brief Creates and selects a one-bar silent region at the timeline cursor.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result add_region();
/**
* @brief Creates and selects a one-bar MIDI clip at the timeline cursor.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result add_midi_clip();
/**
* @brief Removes the selected clip, or the selected track when no clip is selected.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result delete_selection();
/**
* @brief Adjusts timeline zoom within its supported bounds and updates the scroll extent.
*
* @param direction Zoom direction; positive zooms in, nonpositive zooms out.
*/
void zoom(int direction);
/**
* @brief Returns the borrowed project currently displayed by the widget.
* @return Borrowed project pointer.
*/
xtc_project *project() const
{
return project_;
}
/**
* @brief Handles FLTK input events and delegates unhandled events to the base widget.
*
* @param event FLTK event code.
* @return Nonzero if the event was handled, zero otherwise.
*/
int handle(int event) override;
protected:
/**
* @brief Draws the widget's current model, selection, and interaction feedback.
*/
void draw() override;
private:
@ -56,8 +132,22 @@ class SequencerWidget : public Fl_Widget {
uint64_t track_ = UINT64_MAX, clip_ = UINT64_MAX, cursor_ = 0;
int scale_ = 48, drag_x_ = 0, mode_ = 0;
xtc_clip_info original_{};
/**
* @brief Converts a screen x coordinate to a bounded timeline tick snapped to a sixteenth note.
*
* @param px Screen x coordinate in pixels.
* @return Snapped tick within the supported project range.
*/
uint64_t tick(int px) const;
/**
* @brief Reports an edit result, refreshes the widget, and notifies listeners on success.
*
* @param result Result of the attempted operation.
*/
void changed(xtc_result result);
/**
* @brief Resizes the timeline's scrollable area to fit its tracks and clip ends.
*/
void extent();
};
#endif

View file

@ -6,6 +6,18 @@
#include <string.h>
#include "xtc/xtc_project.h"
/**
* @brief Runs the xtc-check command-line entry point.
*
* Supports --version, --help, and --validate PROJECT. With no arguments it
* creates and destroys the application shell to check core startup/shutdown.
* Any other argument count prints usage to stderr.
*
* @param argc Number of command-line arguments, including the program name.
* @param argv Command-line argument vector; argv[0] is the program name.
* @return EXIT_SUCCESS on success, or EXIT_FAILURE for invalid usage,
* validation failure, or core initialization failure.
*/
int main(int argc, char **argv)
{
if (argc == 2 && !strcmp(argv[1], "--version")) {

View file

@ -32,6 +32,15 @@ struct xtc_midi_plan {
uint64_t cutoff[16], next_position, age;
};
/**
* @brief Decodes a complete note or all-notes-off MIDI message into a block event.
*
* @param bytes Borrowed MIDI message bytes.
* @param size Number of message bytes.
* @param offset Sample offset within the current block.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_midi_decode(const unsigned char *bytes, size_t size, unsigned int offset, xtc_midi_event *out)
{
xtc_midi_event e;
@ -54,6 +63,13 @@ xtc_result xtc_midi_decode(const unsigned char *bytes, size_t size, unsigned int
return XTC_OK;
}
/**
* @brief Orders note endpoints by position and endpoint delta for overlap counting.
*
* @param left Borrowed sort element to compare.
* @param right Borrowed sort element to compare.
* @return Negative, zero, or positive according to endpoint ordering.
*/
static int compare_endpoint(const void *left, const void *right)
{
const endpoint *a = left, *b = right;
@ -62,6 +78,11 @@ static int compare_endpoint(const void *left, const void *right)
return a->change - b->change; /* End before start at the same sample. */
}
/**
* @brief Clears live voices, scheduling continuity, and panic suppression.
*
* @param p Instrument plan and its single-owner runtime state.
*/
void xtc_midi_plan_reset(xtc_midi_plan *p)
{
if (!p)
@ -72,6 +93,14 @@ void xtc_midi_plan_reset(xtc_midi_plan *p)
p->age = 0;
}
/**
* @brief Compiles scheduled notes and mixer state into a bounded sine-instrument plan.
*
* @param project Project model; borrowed for this call.
* @param rate Sample rate in hertz.
* @param out Receives the newly allocated object; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_midi_plan_create(const xtc_project *project, unsigned int rate, xtc_midi_plan **out)
{
xtc_midi_plan *p;
@ -155,11 +184,23 @@ xtc_result xtc_midi_plan_create(const xtc_project *project, unsigned int rate, x
return XTC_OK;
}
/**
* @brief Releases scheduled notes and instrument state; accepts NULL.
*
* @param p Instrument plan and its single-owner runtime state.
*/
void xtc_midi_plan_destroy(xtc_midi_plan *p)
{
free(p);
}
/**
* @brief Clears live voices and suppresses already-started scheduled notes on one MIDI channel.
*
* @param p Instrument plan and its single-owner runtime state.
* @param channel MIDI channel, 0 to 15.
* @param position Absolute sample position at the start of the block.
*/
static void channel_off(xtc_midi_plan *p, unsigned int channel, uint64_t position)
{
size_t i;
@ -169,6 +210,12 @@ static void channel_off(xtc_midi_plan *p, unsigned int channel, uint64_t positio
p->cutoff[channel] = position == UINT64_MAX ? position : position + 1;
}
/**
* @brief Silences live voices and suppresses scheduled notes already started at the given position.
*
* @param p Instrument plan and its single-owner runtime state.
* @param position Absolute sample position at the start of the block.
*/
void xtc_midi_plan_panic(xtc_midi_plan *p, uint64_t position)
{
unsigned int c;
@ -179,6 +226,13 @@ void xtc_midi_plan_panic(xtc_midi_plan *p, uint64_t position)
p->next_position = position;
}
/**
* @brief Applies a live MIDI event, stealing the oldest voice when necessary.
*
* @param p Instrument plan and its single-owner runtime state.
* @param e MIDI event to apply.
* @param position Absolute sample position at the start of the block.
*/
static void event(xtc_midi_plan *p, const xtc_midi_event *e, uint64_t position)
{
size_t i, slot = SIZE_MAX;
@ -218,6 +272,15 @@ static void event(xtc_midi_plan *p, const xtc_midi_event *e, uint64_t position)
p->voices[slot].age = p->age++;
}
/**
* @brief Accumulates scheduled and live sine voices over an event-free sample interval.
*
* @param p Instrument plan and its single-owner runtime state.
* @param position Absolute sample position at the start of the block.
* @param out Destination sample buffer with room for the requested frames.
* @param right Destination right-channel buffer; NULL selects mono rendering.
* @param frames Number of audio frames.
*/
static void segment(xtc_midi_plan *p, uint64_t position, float *out, float *right, size_t frames)
{
size_t i, j;
@ -253,6 +316,18 @@ static void segment(xtc_midi_plan *p, uint64_t position, float *out, float *righ
}
}
/**
* @brief Renders scheduled and live MIDI with sample-offset events and track gain and pan.
*
* @param p Instrument plan and its single-owner runtime state.
* @param position Absolute sample position at the start of the block.
* @param events Borrowed MIDI events sorted by sample offset.
* @param count Number of MIDI events.
* @param out Destination sample buffer with room for the requested frames.
* @param right Destination right-channel buffer; NULL selects mono rendering.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_midi_plan_render_stereo(xtc_midi_plan *p, uint64_t position, const xtc_midi_event *events, size_t count,
float *out, float *right, size_t frames)
{
@ -285,6 +360,17 @@ xtc_result xtc_midi_plan_render_stereo(xtc_midi_plan *p, uint64_t position, cons
return XTC_OK;
}
/**
* @brief Renders scheduled and live MIDI to mono without stereo pan attenuation.
*
* @param p Instrument plan and its single-owner runtime state.
* @param position Absolute sample position at the start of the block.
* @param events Borrowed MIDI events sorted by sample offset.
* @param count Number of MIDI events.
* @param out Destination sample buffer with room for the requested frames.
* @param frames Number of audio frames.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_midi_plan_render(xtc_midi_plan *p, uint64_t position, const xtc_midi_event *events, size_t count,
float *out, size_t frames)
{

View file

@ -2,6 +2,16 @@
#include "../graph/graph_internal.h"
#include <string.h>
/**
* @brief Adds a MIDI clip and ensures an instrument output route exists.
*
* @param p Project model; borrowed for this call.
* @param track Stable track identifier.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_add_midi_clip(xtc_project *p, uint64_t track, uint64_t start, uint64_t length, uint64_t *out)
{
xtc_project *c = NULL;
@ -40,6 +50,13 @@ xtc_result xtc_project_add_midi_clip(xtc_project *p, uint64_t track, uint64_t st
return r;
}
/**
* @brief Copies the number of notes in the project.
*
* @param p Project model; borrowed for this call.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_note_count(const xtc_project *p, size_t *out)
{
if (!p || !out)
@ -48,6 +65,14 @@ xtc_result xtc_project_note_count(const xtc_project *p, size_t *out)
return XTC_OK;
}
/**
* @brief Copies a note snapshot selected by dense list index.
*
* @param p Project model; borrowed for this call.
* @param i Zero-based index in the dense list.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_note(const xtc_project *p, size_t i, xtc_note_info *out)
{
if (!p || !out)
@ -58,6 +83,18 @@ xtc_result xtc_project_get_note(const xtc_project *p, size_t i, xtc_note_info *o
return XTC_OK;
}
/**
* @brief Validates a note's MIDI fields and range within a MIDI clip source.
*
* @param p Project model; borrowed for this call.
* @param clip Stable clip identifier.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param pitch MIDI note number, 0 to 127.
* @param velocity MIDI note velocity, 1 to 127.
* @param channel MIDI channel, 0 to 15.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result valid_note(const xtc_project *p, uint64_t clip, uint64_t start, uint64_t length, unsigned int pitch,
unsigned int velocity, unsigned int channel)
{
@ -73,6 +110,19 @@ static xtc_result valid_note(const xtc_project *p, uint64_t clip, uint64_t start
return XTC_OK;
}
/**
* @brief Adds a validated MIDI note with a new stable identifier.
*
* @param p Project model; borrowed for this call.
* @param clip Stable clip identifier.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param pitch MIDI note number, 0 to 127.
* @param velocity MIDI note velocity, 1 to 127.
* @param channel MIDI channel, 0 to 15.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_add_note(xtc_project *p, uint64_t clip, uint64_t start, uint64_t length, unsigned int pitch,
unsigned int velocity, unsigned int channel, uint64_t *out)
{
@ -98,6 +148,18 @@ xtc_result xtc_project_add_note(xtc_project *p, uint64_t clip, uint64_t start, u
return XTC_OK;
}
/**
* @brief Replaces a note's source range, pitch, velocity, and channel after validation.
*
* @param p Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param pitch MIDI note number, 0 to 127.
* @param velocity MIDI note velocity, 1 to 127.
* @param channel MIDI channel, 0 to 15.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_edit_note(xtc_project *p, uint64_t id, uint64_t start, uint64_t length, unsigned int pitch,
unsigned int velocity, unsigned int channel)
{
@ -121,6 +183,13 @@ xtc_result xtc_project_edit_note(xtc_project *p, uint64_t id, uint64_t start, ui
return XTC_OK;
}
/**
* @brief Removes a note selected by stable identifier.
*
* @param p Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_remove_note(xtc_project *p, uint64_t id)
{
size_t i;

View file

@ -17,8 +17,32 @@ typedef struct {
int any_solo;
} xtc_mix_snapshot;
xtc_result xtc_mix_prepare(const xtc_project *, unsigned int rate, xtc_mix_snapshot *);
size_t xtc_mix_track(const xtc_mix_snapshot *, uint64_t id);
/**
* @brief Builds a render-ready snapshot from a project's mix and automation.
*
* @param project Project to snapshot; borrowed and not modified.
* @param rate Sample rate used for automation tick conversion.
* @param m Receives the snapshot.
* @return XTC_OK on success, or a validation/conversion error.
*/
xtc_result xtc_mix_prepare(const xtc_project * project, unsigned int rate, xtc_mix_snapshot * m);
/**
* @brief Finds the dense index of a snapshot track by stable identifier.
*
* @param m Mixer snapshot.
* @param id Stable identifier of the object to find or edit.
* @return Dense track index, or SIZE_MAX if absent.
*/
size_t xtc_mix_track(const xtc_mix_snapshot * m, uint64_t id);
/* side 0/1 stereo, 2 mono (gain only). */
float xtc_mix_level(const xtc_mix_snapshot *, size_t track, uint64_t sample, unsigned int side);
/**
* @brief Evaluates track gain, pan, mute, solo, and automation at a sample position.
*
* @param m Mixer snapshot.
* @param track Dense snapshot track index.
* @param sample Absolute sample position.
* @param side Output side: 0 for left, 1 for right, 2 for mono gain only.
* @return Linear level for the chosen side, or zero for an absent or inaudible track.
*/
float xtc_mix_level(const xtc_mix_snapshot * m, size_t track, uint64_t sample, unsigned int side);
#endif

View file

@ -2,6 +2,13 @@
#include <math.h>
#include <string.h>
/**
* @brief Finds the dense index of a project track by stable ID.
*
* @param p Project to search; must not be NULL.
* @param id Stable track identifier.
* @return Dense track index, or SIZE_MAX when no track has the ID.
*/
static size_t find_track(const xtc_project *p, uint64_t id)
{
size_t i;
@ -11,6 +18,17 @@ static size_t find_track(const xtc_project *p, uint64_t id)
return SIZE_MAX;
}
/**
* @brief Sets a track's gain, pan, mute, and solo controls after validation.
*
* @param p Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param gain Linear gain, 0 to 4 for track controls.
* @param pan Pan position from -1 (left) to 1 (right).
* @param mute Track mute flag, 0 or 1.
* @param solo Track solo flag, 0 or 1.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_mix(xtc_project *p, uint64_t id, float gain, float pan, int mute, int solo)
{
size_t i;
@ -27,6 +45,13 @@ xtc_result xtc_project_set_mix(xtc_project *p, uint64_t id, float gain, float pa
return XTC_OK;
}
/**
* @brief Inserts an automation point, replacing any point at the same track, parameter, and tick.
*
* @param p Project model; borrowed for this call.
* @param a Automation point to read or update.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_automation(xtc_project *p, const xtc_automation_point *a)
{
size_t i;
@ -49,6 +74,15 @@ xtc_result xtc_project_set_automation(xtc_project *p, const xtc_automation_point
return XTC_OK;
}
/**
* @brief Removes the automation point at the specified track, parameter, and tick.
*
* @param p Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param param Automation lane selector.
* @param tick Musical position in ticks.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_remove_automation(xtc_project *p, uint64_t id, xtc_automation_parameter param, uint64_t tick)
{
size_t i;
@ -63,6 +97,13 @@ xtc_result xtc_project_remove_automation(xtc_project *p, uint64_t id, xtc_automa
return XTC_OK;
}
/**
* @brief Copies the number of automation points in the project.
*
* @param p Project model; borrowed for this call.
* @param n Receives the item count.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_automation_count(const xtc_project *p, size_t *n)
{
if (!p || !n)
@ -71,6 +112,14 @@ xtc_result xtc_project_automation_count(const xtc_project *p, size_t *n)
return XTC_OK;
}
/**
* @brief Copies an automation point selected by dense list index.
*
* @param p Project model; borrowed for this call.
* @param i Zero-based index in the dense list.
* @param a Automation point to read or update.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_automation(const xtc_project *p, size_t i, xtc_automation_point *a)
{
if (!p || !a)

View file

@ -2,6 +2,13 @@
#include <stdlib.h>
#include <string.h>
/**
* @brief Finds the dense index of a snapshot track by stable ID.
*
* @param m Snapshot to search; must not be NULL.
* @param id Stable track identifier.
* @return Dense track index, or SIZE_MAX when the ID is absent.
*/
size_t xtc_mix_track(const xtc_mix_snapshot *m, uint64_t id)
{
size_t i;
@ -11,6 +18,13 @@ size_t xtc_mix_track(const xtc_mix_snapshot *m, uint64_t id)
return SIZE_MAX;
}
/**
* @brief Orders automation points by track, parameter, then musical tick.
*
* @param left Borrowed sort element to compare.
* @param right Borrowed sort element to compare.
* @return Negative, zero, or positive according to track, parameter, and tick ordering.
*/
static int compare(const void *left, const void *right)
{
const xtc_mix_point *a = left, *b = right;
@ -21,6 +35,14 @@ static int compare(const void *left, const void *right)
return a->tick < b->tick ? -1 : a->tick != b->tick;
}
/**
* @brief Copies track controls and compiles sorted automation lanes into sample positions.
*
* @param p Project model; borrowed for this call.
* @param rate Sample rate in hertz.
* @param m Mixer snapshot.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_mix_prepare(const xtc_project *p, unsigned int rate, xtc_mix_snapshot *m)
{
size_t i;
@ -55,6 +77,15 @@ xtc_result xtc_mix_prepare(const xtc_project *p, unsigned int rate, xtc_mix_snap
return XTC_OK;
}
/**
* @brief Evaluates a static mixer value or linearly interpolates its automation lane.
*
* @param m Mixer snapshot.
* @param track Dense snapshot track index.
* @param param Automation lane selector.
* @param sample Absolute sample position.
* @return Interpolated automation value, or the track control when its lane is empty.
*/
static float value(const xtc_mix_snapshot *m, size_t track, unsigned int param, uint64_t sample)
{
size_t low = 0, high = m->lengths[track][param], count = high;
@ -77,6 +108,15 @@ static float value(const xtc_mix_snapshot *m, size_t track, unsigned int param,
(p[low].value - p[low - 1].value));
}
/**
* @brief Evaluates track gain, pan, mute, solo, and automation at a sample position.
*
* @param m Mixer snapshot.
* @param track Dense snapshot track index.
* @param sample Absolute sample position.
* @param side Output side: 0 for left, 1 for right, 2 for mono gain only.
* @return Linear level for the chosen side, or zero for an absent or inaudible track.
*/
float xtc_mix_level(const xtc_mix_snapshot *m, size_t track, uint64_t sample, unsigned int side)
{
float gain, pan;

View file

@ -9,6 +9,17 @@ struct xtc_native {
uint64_t next_position;
};
/**
* @brief Checks that a native descriptor is compatible and complete.
*
* Validates the ABI version and structure size, the kind, the presence of all
* callbacks, and that the identifier is a nonempty printable ASCII string.
*
* @param d Descriptor to validate; must not be NULL.
* @return XTC_OK if the descriptor is usable, XTC_ERR_INVALID_ARGUMENT for a
* missing descriptor or invalid fields, or XTC_ERR_UNSUPPORTED for an
* ABI version or structure-size mismatch.
*/
xtc_result xtc_native_validate(const xtc_native_descriptor *d)
{
size_t i;
@ -29,6 +40,16 @@ xtc_result xtc_native_validate(const xtc_native_descriptor *d)
return XTC_ERR_INVALID_ARGUMENT;
}
/**
* @brief Copies a native descriptor and prepares a processor instance on the control thread.
*
* @param d Native processor descriptor.
* @param rate Sample rate in hertz.
* @param frames Number of audio frames.
* @param value Value to encode, write, or apply.
* @param out Receives the newly allocated object; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_native_create(const xtc_native_descriptor *d, double rate, size_t frames, float value, xtc_native **out)
{
xtc_native *p;
@ -57,6 +78,11 @@ xtc_result xtc_native_create(const xtc_native_descriptor *d, double rate, size_t
return XTC_OK;
}
/**
* @brief Destroys native state and releases the host wrapper; accepts NULL.
*
* @param p Native processor instance.
*/
void xtc_native_destroy(xtc_native *p)
{
if (p) {
@ -65,6 +91,11 @@ void xtc_native_destroy(xtc_native *p)
}
}
/**
* @brief Resets native state and transport continuity; accepts NULL.
*
* @param p Native processor instance.
*/
void xtc_native_reset(xtc_native *p)
{
if (p) {
@ -73,6 +104,13 @@ void xtc_native_reset(xtc_native *p)
}
}
/**
* @brief Processes a bounded native block, resetting on discontinuity and silencing callback failures.
*
* @param p Native processor instance.
* @param b Borrowed processing buffers and block metadata.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_native_process(xtc_native *p, const xtc_native_block *b)
{
size_t i;

View file

@ -19,6 +19,12 @@ typedef struct {
size_t count;
} reader;
/**
* @brief Reads and tokenizes one bounded printable-ASCII project record.
*
* @param input Reader holding the file and reusable line/token buffers.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result line(reader *input)
{
size_t used = 0U;
@ -54,6 +60,13 @@ static xtc_result line(reader *input)
return input->count == 0U ? XTC_ERR_FORMAT : XTC_OK;
}
/**
* @brief Parses an unsigned decimal integer with overflow detection.
*
* @param text NUL-terminated encoded text.
* @param value Receives the decoded value.
* @return Nonzero for a valid decimal value, zero for empty, malformed, or overflowing input.
*/
static int number(const char *text, uint64_t *value)
{
uint64_t result = 0U;
@ -73,6 +86,12 @@ static int number(const char *text, uint64_t *value)
return 1;
}
/**
* @brief Converts one ASCII hexadecimal digit to its numeric value.
*
* @param byte ASCII character to decode.
* @return Digit value from 0 to 15, or -1 for a non-hexadecimal character.
*/
static int hex_digit(char byte)
{
if (byte >= '0' && byte <= '9')
@ -84,6 +103,13 @@ static int hex_digit(char byte)
return -1;
}
/**
* @brief Decodes a hexadecimal project name and validates its UTF-8 contents.
*
* @param text NUL-terminated encoded text.
* @param name NUL-terminated name.
* @return Nonzero for a valid decoded name, zero for malformed or invalid input.
*/
static int decode_name(const char *text, char *name)
{
size_t length = strlen(text), i;
@ -99,6 +125,13 @@ static int decode_name(const char *text, char *name)
return xtc_project_valid_name(name);
}
/**
* @brief Decodes eight hexadecimal IEEE-754 bits into a finite float.
*
* @param text NUL-terminated encoded text.
* @param value Receives the decoded value.
* @return Nonzero for a finite decoded float, zero for an unsupported representation or invalid input.
*/
static int decode_float(const char *text, float *value)
{
uint32_t bits = 0U;
@ -131,6 +164,13 @@ static int decode_float(const char *text, float *value)
return XTC_ERR_FORMAT; \
} while (0)
/**
* @brief Parses versioned project records into a temporary project.
*
* @param input Reader holding the file and reusable line/token buffers.
* @param project Project model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result read_project(reader *input, xtc_project *project)
{
uint64_t version, value, next_id, node_count, edge_count, format_version;
@ -317,6 +357,13 @@ static xtc_result read_project(reader *input, xtc_project *project)
return xtc_project_validate(project);
}
/**
* @brief Writes a name as hexadecimal bytes without a terminator.
*
* @param file Open file stream at the required read or write position.
* @param name NUL-terminated name.
* @return Nonzero if every encoded byte was written, zero on a write failure.
*/
static int write_name(FILE *file, const char *name)
{
const unsigned char *cursor = (const unsigned char *)name;
@ -326,6 +373,13 @@ static int write_name(FILE *file, const char *name)
return fputc('\n', file) != EOF;
}
/**
* @brief Serializes project metadata and graph records in the current format.
*
* @param file Open file stream at the required read or write position.
* @param project Project model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
static xtc_result write_project(FILE *file, const xtc_project *project)
{
const xtc_graph *graph = project->graph;
@ -402,6 +456,12 @@ static xtc_result write_project(FILE *file, const xtc_project *project)
return fputs("END\n", file) == EOF ? XTC_ERR_IO : XTC_OK;
}
/**
* @brief Measures a file path up to the supported 4096-byte bound.
*
* @param path NUL-terminated file or asset path.
* @return Path length, capped at 4096; zero for NULL.
*/
static size_t path_length(const char *path)
{
size_t size = 0U;
@ -412,6 +472,13 @@ static size_t path_length(const char *path)
return size == 4096U ? 0U : size;
}
/**
* @brief Loads and validates a project before transactionally replacing the current state.
*
* @param project Project model; borrowed for this call.
* @param path NUL-terminated file or asset path.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_load(xtc_project *project, const char *path)
{
xtc_project *candidate = NULL;
@ -436,6 +503,13 @@ xtc_result xtc_project_load(xtc_project *project, const char *path)
return result;
}
/**
* @brief Saves a versioned project through a synced temporary file and atomic rename.
*
* @param project Project model; borrowed for this call.
* @param path NUL-terminated file or asset path.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_save(const xtc_project *project, const char *path)
{
size_t length = path_length(path);

View file

@ -10,6 +10,12 @@ struct xtc_history {
size_t next_node;
};
/**
* @brief Raises history's identifier high-water marks to include the project's allocations.
*
* @param h Snapshot history handle.
* @param p Project model; borrowed for this call.
*/
static void remember_ids(xtc_history *h, const xtc_project *p)
{
if (p->next_track_id > h->next_track)
@ -22,6 +28,11 @@ static void remember_ids(xtc_history *h, const xtc_project *p)
h->next_node = p->graph->next_id;
}
/**
* @brief Releases all retained snapshots and history storage; accepts NULL.
*
* @param h Snapshot history handle.
*/
void xtc_history_destroy(xtc_history *h)
{
size_t i;
@ -32,6 +43,13 @@ void xtc_history_destroy(xtc_history *h)
free(h);
}
/**
* @brief Creates snapshot history with an independent copy of the initial project.
*
* @param p Project model; borrowed for this call.
* @param out Receives the newly allocated object; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_history_create(const xtc_project *p, xtc_history **out)
{
xtc_history *h;
@ -55,6 +73,13 @@ xtc_result xtc_history_create(const xtc_project *p, xtc_history **out)
return XTC_OK;
}
/**
* @brief Appends a project snapshot, discarding redo states and evicting the oldest state if full.
*
* @param h Snapshot history handle.
* @param p Project model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_history_record(xtc_history *h, const xtc_project *p)
{
xtc_project *copy;
@ -79,11 +104,26 @@ xtc_result xtc_history_record(xtc_history *h, const xtc_project *p)
return XTC_OK;
}
/**
* @brief Checks whether an adjacent undo or redo snapshot exists.
*
* @param h Snapshot history handle.
* @param redo Zero for undo, one for redo.
* @return Nonzero when the requested history state exists, zero otherwise.
*/
static int available(const xtc_history *h, int redo)
{
return redo ? h->cursor + 1 < h->count : h->cursor > 0;
}
/**
* @brief Clones the adjacent undo or redo state without moving the history cursor.
*
* @param h Snapshot history handle.
* @param redo Zero for undo, one for redo.
* @param out Receives the newly allocated object; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_history_checkout(const xtc_history *h, int redo, xtc_project **out)
{
if (!out)
@ -105,6 +145,13 @@ xtc_result xtc_history_checkout(const xtc_history *h, int redo, xtc_project **ou
}
}
/**
* @brief Moves the history cursor after the checked-out project has been installed.
*
* @param h Snapshot history handle.
* @param redo Zero for undo, one for redo.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_history_accept(xtc_history *h, int redo)
{
if (!h || (redo != 0 && redo != 1))

View file

@ -5,6 +5,12 @@
#include <math.h>
#include <string.h>
/**
* @brief Checks for a nonempty UTF-8 name of at most 63 bytes without ASCII controls.
*
* @param name NUL-terminated name.
* @return Nonzero for a valid name, zero otherwise.
*/
int xtc_project_valid_name(const char *name)
{
size_t length = 0U, i = 0U;
@ -43,6 +49,13 @@ int xtc_project_valid_name(const char *name)
return 1;
}
/**
* @brief Finds a project's dense track index by stable identifier.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return Dense track index, or SIZE_MAX if absent.
*/
static size_t track_index(const xtc_project *project, uint64_t id)
{
size_t i;
@ -52,6 +65,13 @@ static size_t track_index(const xtc_project *project, uint64_t id)
return SIZE_MAX;
}
/**
* @brief Finds a project's dense clip index by stable identifier.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return Dense clip index, or SIZE_MAX if absent.
*/
static size_t clip_index(const xtc_project *project, uint64_t id)
{
size_t i;
@ -61,12 +81,27 @@ static size_t clip_index(const xtc_project *project, uint64_t id)
return SIZE_MAX;
}
/**
* @brief Checks that a nonempty timeline range and its source slice fit their bounds.
*
* @param start Timeline start tick.
* @param length Nonzero duration in ticks.
* @param offset Source offset in ticks.
* @param source Source duration in ticks.
* @return Nonzero if both timeline and source bounds are valid, zero otherwise.
*/
static int valid_range(uint64_t start, uint64_t length, uint64_t offset, uint64_t source)
{
return start <= XTC_PROJECT_MAX_TICK && length != 0U && length <= XTC_PROJECT_MAX_TICK - start &&
source <= XTC_PROJECT_MAX_TICK && offset <= source && length <= source - offset;
}
/**
* @brief Allocates an empty project with an output-only graph policy and 120 BPM tempo.
*
* @param out_project Receives the newly allocated project; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_create(xtc_project **out_project)
{
xtc_project *project;
@ -88,6 +123,11 @@ xtc_result xtc_project_create(xtc_project **out_project)
return XTC_OK;
}
/**
* @brief Releases a project and its graph; accepts NULL.
*
* @param project Project model; borrowed for this call.
*/
void xtc_project_destroy(xtc_project *project)
{
if (project != NULL) {
@ -96,11 +136,24 @@ void xtc_project_destroy(xtc_project *project)
}
}
/**
* @brief Returns the project's borrowed graph, or NULL for a NULL project.
*
* @param project Project model; borrowed for this call.
* @return Borrowed graph valid until project mutation or destruction, or NULL for NULL.
*/
const xtc_graph *xtc_project_graph(const xtc_project *project)
{
return project == NULL ? NULL : project->graph;
}
/**
* @brief Copies and validates a graph before replacing the project's graph.
*
* @param project Project model; borrowed for this call.
* @param source Graph model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_graph(xtc_project *project, const xtc_graph *source)
{
xtc_graph *copy;
@ -126,6 +179,13 @@ xtc_result xtc_project_set_graph(xtc_project *project, const xtc_graph *source)
return XTC_OK;
}
/**
* @brief Allocates an independent copy of project metadata and its graph.
*
* @param source Project model; borrowed for this call.
* @param out_project Receives the newly allocated project; caller owns it on success.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_clone(const xtc_project *source, xtc_project **out_project)
{
xtc_project *copy;
@ -149,6 +209,14 @@ xtc_result xtc_project_clone(const xtc_project *source, xtc_project **out_projec
return XTC_OK;
}
/**
* @brief Copies the current track and clip counts.
*
* @param project Project model; borrowed for this call.
* @param tracks Receives the number of tracks.
* @param clips Receives the number of clips.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_counts(const xtc_project *project, size_t *tracks, size_t *clips)
{
if (project == NULL || tracks == NULL || clips == NULL)
@ -158,6 +226,14 @@ xtc_result xtc_project_get_counts(const xtc_project *project, size_t *tracks, si
return XTC_OK;
}
/**
* @brief Copies a track snapshot selected by dense list index.
*
* @param project Project model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param track Receives a copy of the track metadata.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_track(const xtc_project *project, size_t index, xtc_track_info *track)
{
if (project == NULL || track == NULL)
@ -168,6 +244,14 @@ xtc_result xtc_project_get_track(const xtc_project *project, size_t index, xtc_t
return XTC_OK;
}
/**
* @brief Copies a clip snapshot selected by dense list index.
*
* @param project Project model; borrowed for this call.
* @param index Zero-based index in the dense list.
* @param clip Receives a copy of the clip metadata.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_clip(const xtc_project *project, size_t index, xtc_clip_info *clip)
{
if (project == NULL || clip == NULL)
@ -178,6 +262,14 @@ xtc_result xtc_project_get_clip(const xtc_project *project, size_t index, xtc_cl
return XTC_OK;
}
/**
* @brief Copies a clip snapshot selected by stable identifier.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param clip Receives a copy of the clip metadata.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_find_clip(const xtc_project *project, uint64_t id, xtc_clip_info *clip)
{
if (project == NULL)
@ -185,6 +277,14 @@ xtc_result xtc_project_find_clip(const xtc_project *project, uint64_t id, xtc_cl
return xtc_project_get_clip(project, clip_index(project, id), clip);
}
/**
* @brief Appends a track with unity gain and a new stable identifier.
*
* @param project Project model; borrowed for this call.
* @param name NUL-terminated name.
* @param id Receives the new stable identifier.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_add_track(xtc_project *project, const char *name, uint64_t *id)
{
xtc_track_info track = {0};
@ -200,6 +300,13 @@ xtc_result xtc_project_add_track(xtc_project *project, const char *name, uint64_
return XTC_OK;
}
/**
* @brief Removes a track and its clips, notes, and automation.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_remove_track(xtc_project *project, uint64_t id)
{
size_t index, i, count = 0U;
@ -228,6 +335,17 @@ xtc_result xtc_project_remove_track(xtc_project *project, uint64_t id)
return XTC_OK;
}
/**
* @brief Appends a silent region on an existing track with a new stable identifier.
*
* @param project Project model; borrowed for this call.
* @param track Stable track identifier.
* @param name NUL-terminated name.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param id Receives the new stable identifier.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_add_clip(xtc_project *project, uint64_t track, const char *name, uint64_t start, uint64_t length,
uint64_t *id)
{
@ -248,6 +366,13 @@ xtc_result xtc_project_add_clip(xtc_project *project, uint64_t track, const char
return XTC_OK;
}
/**
* @brief Removes a clip and its notes.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_remove_clip(xtc_project *project, uint64_t id)
{
size_t index;
@ -269,6 +394,17 @@ xtc_result xtc_project_remove_clip(xtc_project *project, uint64_t id)
return XTC_OK;
}
/**
* @brief Atomically moves or trims a clip within its existing source bounds.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param track Stable track identifier.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param offset Sample offset within the current block.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_edit_clip(xtc_project *project, uint64_t id, uint64_t track, uint64_t start, uint64_t length,
uint64_t offset)
{
@ -287,11 +423,24 @@ xtc_result xtc_project_edit_clip(xtc_project *project, uint64_t id, uint64_t tra
return XTC_OK;
}
/**
* @brief Returns the constant tempo in milli-BPM, or zero for a NULL project.
*
* @param project Project model; borrowed for this call.
* @return Tempo in milli-BPM, or zero for NULL.
*/
unsigned int xtc_project_tempo(const xtc_project *project)
{
return project == NULL ? 0U : project->tempo;
}
/**
* @brief Sets the constant tempo within the range 20000 to 300000 milli-BPM.
*
* @param project Project model; borrowed for this call.
* @param tempo Tempo in milli-BPM.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_tempo(xtc_project *project, unsigned int tempo)
{
if (project == NULL || tempo < 20000U || tempo > 300000U)
@ -300,6 +449,15 @@ xtc_result xtc_project_set_tempo(xtc_project *project, unsigned int tempo)
return XTC_OK;
}
/**
* @brief Converts a musical tick to an absolute sample position, rounding down.
*
* @param project Project model; borrowed for this call.
* @param tick Musical position in ticks.
* @param rate Sample rate in hertz.
* @param sample Receives the sample position.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_tick_to_sample(const xtc_project *project, uint64_t tick, unsigned int rate, uint64_t *sample)
{
if (project == NULL || sample == NULL || rate == 0U || rate > 768000U)
@ -310,6 +468,15 @@ xtc_result xtc_project_tick_to_sample(const xtc_project *project, uint64_t tick,
return XTC_OK;
}
/**
* @brief Converts an absolute sample position to a musical tick, rounding down.
*
* @param project Project model; borrowed for this call.
* @param sample Absolute sample position.
* @param rate Sample rate in hertz.
* @param tick Receives the musical tick.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_sample_to_tick(const xtc_project *project, uint64_t sample, unsigned int rate, uint64_t *tick)
{
uint64_t scale, value, divisor, whole;
@ -327,6 +494,12 @@ xtc_result xtc_project_sample_to_tick(const xtc_project *project, uint64_t sampl
return XTC_OK;
}
/**
* @brief Validates project IDs, timeline ranges, names, graph, notes, and automation.
*
* @param project Project model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_validate(const xtc_project *project)
{
size_t i, j;
@ -395,6 +568,14 @@ xtc_result xtc_project_validate(const xtc_project *project)
return XTC_OK;
}
/**
* @brief Sets an absolute audio asset reference, or clears it to make a region silent.
*
* @param project Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param path NUL-terminated file or asset path.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_clip_asset(xtc_project *project, uint64_t id, const char *path)
{
size_t index, length = 0U, i;

View file

@ -19,6 +19,18 @@ struct xtc_project {
xtc_clip_info clips[XTC_PROJECT_MAX_CLIPS];
};
/**
* @brief Checks for a nonempty UTF-8 name of at most 63 bytes without ASCII controls.
*
* @param name NUL-terminated name.
* @return Nonzero for a valid name, zero otherwise.
*/
int xtc_project_valid_name(const char *name);
/**
* @brief Validates project IDs, timeline ranges, names, graph, notes, and automation.
*
* @param project Project model; borrowed for this call.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_validate(const xtc_project *project);
#endif