- AppEngine: save_project -> export_project_path(explicit path); project_modified removed (write-through has no dirty flag) - File menu's save/save-as are the export path (extension-dispatched ove/otio/fcpxml); the library row export path is unchanged - facade project_save/is_modified stay frozen, documented as the legacy export surface - docs: M13 D5 checked off; M12 inventory updated
982 lines
37 KiB
Rust
982 lines
37 KiB
Rust
// Oak Video Editor - Non-Linear Video Editor
|
|
// Copyright (C) 2026 Oak Team
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//! The app's pure-C surface of the `liboakengine` dylib.
|
|
//!
|
|
//! The app links the built `liboakengine.dylib` (see `build.rs`) and calls
|
|
//! ONLY its `oakengine_*` C ABI — it never depends on the `oakengine` crate
|
|
//! as an rlib. This module declares every exported function the real engine
|
|
//! binding uses, with the exact signatures from the facade's `#[no_mangle]`
|
|
//! exports (`crates/oakengine/src/*.rs`). The facade wraps the module
|
|
//! C ABIs that are also embedded in the same dylib (`oakundo_*`,
|
|
//! `oakcommon_*`, ...), so everything the app touches resolves through an
|
|
//! `oakengine_*` symbol.
|
|
//!
|
|
//! # Handle layout mirrors
|
|
//!
|
|
//! The facade's opaque `OakEngine*` handle types are thin `#[repr(C)]`
|
|
//! newtypes around one module [`CHandle`] value (see
|
|
//! `crates/oakengine/src/handle.rs`), and boxes created with
|
|
//! `box_handle`/`free_box` live in the heap. The dylib ABI passes those
|
|
//! boxes as opaque pointers, but a pure-C consumer that needs to (a) build
|
|
//! a project box from a module handle (interchange load) or (b) free a
|
|
//! borrowed handle box (sequences / clips the facade returns but has no
|
|
//! `oakengine_*_free` for) must know the box layout. The mirrors below
|
|
//! reproduce it exactly (identical `repr(C)` field layout), so boxes
|
|
//! created by the facade can be read/freed from the app and vice versa.
|
|
//!
|
|
//! The module handle type itself is the frozen `{ctx, addref, release,
|
|
//! abi_version}` value handle (`include/common/handle.h`); `release` is
|
|
//! what `free_box` calls before deallocating the box.
|
|
|
|
use std::ffi::{c_char, c_int, c_void};
|
|
|
|
/// The module value handle (`{ctx, addref, release, abi_version}`), mirror
|
|
/// of `oakcore_rs::handle::CHandle` / `include/common/handle.h`.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct CHandle {
|
|
/// Opaque refcounted box pointer.
|
|
pub ctx: *mut c_void,
|
|
/// Atomic increment.
|
|
pub addref: Option<unsafe extern "C" fn(*mut c_void)>,
|
|
/// Atomic decrement; destroys at zero.
|
|
pub release: Option<unsafe extern "C" fn(*mut c_void)>,
|
|
/// ABI version.
|
|
pub abi_version: u32,
|
|
}
|
|
|
|
impl CHandle {
|
|
/// Whether this is the empty (zero) handle.
|
|
pub fn is_null(&self) -> bool {
|
|
self.ctx.is_null()
|
|
}
|
|
}
|
|
|
|
/// Opaque engine handle boxes, mirroring the facade's `engine_handle!`
|
|
/// newtypes (one `CHandle` per box). Only the types the app actually
|
|
/// touches are declared.
|
|
macro_rules! engine_handle {
|
|
($($name:ident),* $(,)?) => {
|
|
$(
|
|
/// Opaque engine handle: a `#[repr(C)]` box holding one module
|
|
/// [`CHandle`].
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy)]
|
|
pub struct $name {
|
|
/// The wrapped module handle.
|
|
pub handle: CHandle,
|
|
}
|
|
|
|
impl HandleBox for $name {
|
|
fn boxed_new(handle: CHandle) -> Self {
|
|
$name { handle }
|
|
}
|
|
fn handle(&self) -> CHandle {
|
|
self.handle
|
|
}
|
|
}
|
|
)*
|
|
};
|
|
}
|
|
|
|
engine_handle! {
|
|
OakEngineAudioBuffer,
|
|
OakEngineClip,
|
|
OakEngineEncodingParams,
|
|
OakEngineFootage,
|
|
OakEngineFrame,
|
|
OakEngineNode,
|
|
OakEngineProject,
|
|
OakEngineRenderer,
|
|
OakEngineSequence,
|
|
OakEngineTask,
|
|
}
|
|
|
|
/// Uniform construction/extraction surface of the engine opaque boxes.
|
|
pub trait HandleBox: Sized {
|
|
/// Build the box from a module handle.
|
|
fn boxed_new(handle: CHandle) -> Self;
|
|
/// Extract the wrapped module handle (copy).
|
|
fn handle(&self) -> CHandle;
|
|
}
|
|
|
|
/// Allocate a heap box for a module handle and return its raw pointer.
|
|
/// The box must later be released with [`free_box`] or a consuming
|
|
/// `oakengine_*_free` export.
|
|
///
|
|
/// # Safety
|
|
/// The handle must be a live module handle (e.g. from a facade export that
|
|
/// hands one over for the app to box).
|
|
pub unsafe fn box_handle<T: HandleBox>(handle: CHandle) -> *mut T {
|
|
// SAFETY: the caller passes a live handle; the box is managed by the
|
|
// C ABI consumers from here on.
|
|
Box::into_raw(Box::new(T::boxed_new(handle)))
|
|
}
|
|
|
|
/// Dereference an engine opaque box and copy out its module handle.
|
|
/// Returns `None` for a NULL pointer or an empty handle.
|
|
///
|
|
/// # Safety
|
|
/// `ptr` must point to a live box created by [`box_handle`] or by the
|
|
/// facade (or be NULL).
|
|
pub unsafe fn unbox<T: HandleBox>(ptr: *const T) -> Option<CHandle> {
|
|
// SAFETY: see the function docs.
|
|
if ptr.is_null() {
|
|
return None;
|
|
}
|
|
let h = (*ptr).handle();
|
|
if h.is_null() {
|
|
None
|
|
} else {
|
|
Some(h)
|
|
}
|
|
}
|
|
|
|
/// Free a box created by [`box_handle`] (or returned by the facade):
|
|
/// release the module handle (via its `release` function pointer) and
|
|
/// deallocate the box. NULL and empty handles are no-ops. After the call
|
|
/// `ptr` is dangling; the caller must not use it again.
|
|
///
|
|
/// # Safety
|
|
/// `ptr` must be a pointer previously returned by [`box_handle`] or by the
|
|
/// facade (or NULL) and must not be freed twice.
|
|
pub unsafe fn free_box<T: HandleBox>(ptr: *mut T) {
|
|
// SAFETY: see the function docs.
|
|
if ptr.is_null() {
|
|
return;
|
|
}
|
|
let handle = (*ptr).handle();
|
|
if let Some(release) = handle.release {
|
|
release(handle.ctx);
|
|
}
|
|
drop(Box::from_raw(ptr));
|
|
}
|
|
|
|
/// `engine/include/oakengine/videoparams.h` — POD mirror of VideoParams'
|
|
/// user-facing fields (Rust mirror of `oak_video_params`; the facade's
|
|
/// `oakengine::common::OakVideoParamsPod`).
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy)]
|
|
pub struct OakVideoParamsPod {
|
|
/// Width.
|
|
pub width: c_int,
|
|
/// Height.
|
|
pub height: c_int,
|
|
/// Frame duration numerator (e.g. 1001/30000 s).
|
|
pub time_base_num: c_int,
|
|
/// Frame duration denominator.
|
|
pub time_base_den: c_int,
|
|
/// PixelFormat::Format value.
|
|
pub format: c_int,
|
|
/// Pixel aspect numerator.
|
|
pub pixel_aspect_num: c_int,
|
|
/// Pixel aspect denominator.
|
|
pub pixel_aspect_den: c_int,
|
|
/// Interlacing value.
|
|
pub interlacing: c_int,
|
|
/// ColorRange value.
|
|
pub color_range: c_int,
|
|
/// Preview resolution divider (1 = full).
|
|
pub divider: c_int,
|
|
/// VideoParams::Type value.
|
|
pub video_type: c_int,
|
|
/// 0/1 premultiplied alpha.
|
|
pub premultiplied_alpha: c_int,
|
|
}
|
|
|
|
/// The task event callback the module subscription invokes on the task's
|
|
/// own thread (`oaktask` C ABI, `include/task/task.h`).
|
|
pub type OakTaskEventFn = unsafe extern "C" fn(event_id: c_int, value: f64, userdata: *mut c_void);
|
|
|
|
// The `oakengine_*` C ABI surface the app binds. Signatures mirror the
|
|
// facade's `#[no_mangle] pub extern "C"` exports verbatim (the facade
|
|
// declares a few of them without `unsafe`; calling any extern-block item
|
|
// still requires an unsafe context on the current toolchain, so the call
|
|
// sites in `real.rs` carry their own `unsafe` blocks).
|
|
//
|
|
// String outputs follow the engine buf/size convention: the return value
|
|
// is the required length excluding the terminating NUL; negative values
|
|
// are error codes.
|
|
#[link(name = "oakengine")]
|
|
unsafe extern "C" {
|
|
// -- oakengine::codec (encoding formats + params) --
|
|
|
|
/// `oakengine_encoding_format_count` — number of export formats.
|
|
pub fn oakengine_encoding_format_count() -> c_int;
|
|
/// `oakengine_encoding_format_name` (buf/size; -1 invalid).
|
|
pub fn oakengine_encoding_format_name(
|
|
format: c_int,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_encoding_format_extension` (buf/size).
|
|
pub fn oakengine_encoding_format_extension(
|
|
format: c_int,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_encoding_format_video_codec_at` — codec id, -1 invalid.
|
|
pub fn oakengine_encoding_format_video_codec_at(format: c_int, index: c_int) -> c_int;
|
|
/// `oakengine_encoding_format_audio_codec_at` — codec id, -1 invalid.
|
|
pub fn oakengine_encoding_format_audio_codec_at(format: c_int, index: c_int) -> c_int;
|
|
/// `oakengine_encoding_params_create` — owned params box.
|
|
pub fn oakengine_encoding_params_create() -> *mut OakEngineEncodingParams;
|
|
/// `oakengine_encoding_params_destroy` — consuming free.
|
|
pub fn oakengine_encoding_params_destroy(params: *mut OakEngineEncodingParams);
|
|
/// `oakengine_encoding_params_set_filename`.
|
|
pub fn oakengine_encoding_params_set_filename(
|
|
params: *mut OakEngineEncodingParams,
|
|
filename: *const c_char,
|
|
) -> c_int;
|
|
/// `oakengine_encoding_params_set_format` — rejects out-of-range values.
|
|
pub fn oakengine_encoding_params_set_format(
|
|
params: *mut OakEngineEncodingParams,
|
|
format: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_encoding_params_enable_video` — copy the POD-carryable
|
|
/// fields of `video` and enable the video track.
|
|
pub fn oakengine_encoding_params_enable_video(
|
|
params: *mut OakEngineEncodingParams,
|
|
video: *const OakVideoParamsPod,
|
|
codec: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_encoding_params_enable_audio`.
|
|
pub fn oakengine_encoding_params_enable_audio(
|
|
params: *mut OakEngineEncodingParams,
|
|
sample_rate: c_int,
|
|
channel_layout: u64,
|
|
sample_format: c_int,
|
|
codec: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_encoding_params_set_export_length`.
|
|
pub fn oakengine_encoding_params_set_export_length(
|
|
params: *mut OakEngineEncodingParams,
|
|
num: c_int,
|
|
den: c_int,
|
|
);
|
|
|
|
// -- oakengine::common (config) --
|
|
|
|
/// `oakengine_config_get_string` — read a config string; a missing key
|
|
/// reads as an empty string.
|
|
pub fn oakengine_config_get_string(
|
|
key: *const c_char,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_config_set_string` — write a string value.
|
|
pub fn oakengine_config_set_string(key: *const c_char, value: *const c_char) -> c_int;
|
|
|
|
// -- oakengine::storage (write-through session state) --
|
|
|
|
/// `oakengine_storage_flush` — flush every bound project and stop the
|
|
/// snapshot thread (the app calls it on exit).
|
|
pub fn oakengine_storage_flush() -> c_int;
|
|
/// `oakengine_storage_is_bound` — 1 when the project is bound to a
|
|
/// library session.
|
|
pub fn oakengine_storage_is_bound(project: *mut OakEngineProject) -> c_int;
|
|
/// `oakengine_storage_last_error` — the last write-through / snapshot
|
|
/// error (buf/size; empty when none or not bound).
|
|
pub fn oakengine_storage_last_error(
|
|
project: *mut OakEngineProject,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
|
|
// -- oakengine::library (project manager, M13 D4) --
|
|
|
|
/// `oakengine_library_list` — the library rows as a JSON array
|
|
/// (buf/size), most recently modified first; `"[]"` with storage off.
|
|
pub fn oakengine_library_list(buf: *mut c_char, buf_size: c_int) -> c_int;
|
|
/// `oakengine_library_create` — create a blank project row; reports its
|
|
/// uuid (buf/size on `out_uuid`; the return value is the uuid length,
|
|
/// negative on error).
|
|
pub fn oakengine_library_create(
|
|
name: *const c_char,
|
|
out_uuid: *mut c_char,
|
|
out_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_library_delete` — delete a row by uuid.
|
|
pub fn oakengine_library_delete(uuid: *const c_char) -> c_int;
|
|
/// `oakengine_library_rename` — rename a row.
|
|
pub fn oakengine_library_rename(uuid: *const c_char, name: *const c_char) -> c_int;
|
|
/// `oakengine_library_duplicate` — copy a row (history included);
|
|
/// reports the new uuid like `oakengine_library_create`.
|
|
pub fn oakengine_library_duplicate(
|
|
uuid: *const c_char,
|
|
name: *const c_char,
|
|
out_uuid: *mut c_char,
|
|
out_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_library_import` — import a `.ove`/`.otio`/`.fcpxml` file
|
|
/// as a new row; reports the new uuid like `oakengine_library_create`.
|
|
pub fn oakengine_library_import(
|
|
path: *const c_char,
|
|
out_uuid: *mut c_char,
|
|
out_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_library_export` — export a row to `path` (format by
|
|
/// extension).
|
|
pub fn oakengine_library_export(uuid: *const c_char, path: *const c_char) -> c_int;
|
|
/// `oakengine_project_load_library` — load a library row into a fresh
|
|
/// project shell (same contract as `oakengine_project_load`); binds the
|
|
/// project to the library session.
|
|
pub fn oakengine_project_load_library(
|
|
self_: *mut OakEngineProject,
|
|
uuid: *const c_char,
|
|
err: *mut c_char,
|
|
err_size: c_int,
|
|
) -> c_int;
|
|
|
|
// -- oakengine::node (project) --
|
|
|
|
/// `oakengine_project_create` — owned project box (no content yet).
|
|
pub fn oakengine_project_create() -> *mut OakEngineProject;
|
|
/// `oakengine_project_free` — consuming free of an owned project box.
|
|
pub fn oakengine_project_free(self_: *mut OakEngineProject);
|
|
/// `oakengine_project_new` — initialize a blank project.
|
|
pub fn oakengine_project_new(self_: *mut OakEngineProject) -> c_int;
|
|
/// `oakengine_project_load` — load from `path`; fills `err` (buf/size)
|
|
/// on failure.
|
|
pub fn oakengine_project_load(
|
|
self_: *mut OakEngineProject,
|
|
path: *const c_char,
|
|
err: *mut c_char,
|
|
err_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_project_save` — write the project to `path` (or the
|
|
/// recorded filename when NULL). Legacy manual-save ABI, now used only
|
|
/// as the .ove export path (导出工程文件…); the write-through library is
|
|
/// the primary persistence.
|
|
pub fn oakengine_project_save(self_: *mut OakEngineProject, path: *const c_char) -> c_int;
|
|
/// `oakengine_project_name` (buf/size).
|
|
pub fn oakengine_project_name(
|
|
self_: *const OakEngineProject,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_project_filename` (buf/size).
|
|
pub fn oakengine_project_filename(
|
|
self_: *const OakEngineProject,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_project_footage_count`.
|
|
pub fn oakengine_project_footage_count(self_: *const OakEngineProject) -> c_int;
|
|
/// `oakengine_project_import_footage` — probe and add to the root
|
|
/// folder; owned footage box (free with `oakengine_footage_free`).
|
|
pub fn oakengine_project_import_footage(
|
|
self_: *mut OakEngineProject,
|
|
path: *const c_char,
|
|
) -> *mut OakEngineFootage;
|
|
/// `oakengine_footage_free` — release a footage handle.
|
|
pub fn oakengine_footage_free(self_: *mut OakEngineFootage);
|
|
/// `oakengine_sequence_add_footage_clip_ex` — place a clip of
|
|
/// `footage` on the track, skipping the unenforceable same-project
|
|
/// check (sequences live in their own scratch project — documented
|
|
/// module deviation; M12 P0 montage path). Owned clip box.
|
|
pub fn oakengine_sequence_add_footage_clip_ex(
|
|
self_: *mut OakEngineSequence,
|
|
footage: *mut OakEngineFootage,
|
|
track_type: c_int,
|
|
track_index: c_int,
|
|
in_: i64,
|
|
out: i64,
|
|
media_in: i64,
|
|
) -> *mut OakEngineClip;
|
|
/// `oakengine_project_footage_at` — boxed footage node at `index` (free
|
|
/// with `oakengine_node_free`); NULL for an invalid index.
|
|
pub fn oakengine_project_footage_at(
|
|
self_: *const OakEngineProject,
|
|
index: c_int,
|
|
) -> *mut OakEngineNode;
|
|
/// `oakengine_project_footage_filename` (buf/size).
|
|
pub fn oakengine_project_footage_filename(
|
|
self_: *const OakEngineProject,
|
|
index: c_int,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_project_can_undo`.
|
|
pub fn oakengine_project_can_undo(self_: *const OakEngineProject) -> c_int;
|
|
/// `oakengine_project_can_redo`.
|
|
pub fn oakengine_project_can_redo(self_: *const OakEngineProject) -> c_int;
|
|
/// `oakengine_project_undo`.
|
|
pub fn oakengine_project_undo(self_: *mut OakEngineProject) -> c_int;
|
|
/// `oakengine_project_redo`.
|
|
pub fn oakengine_project_redo(self_: *mut OakEngineProject) -> c_int;
|
|
/// `oakengine_project_sequence_count`.
|
|
pub fn oakengine_project_sequence_count(self_: *const OakEngineProject) -> c_int;
|
|
/// `oakengine_project_sequence_at` — borrowed sequence box (free with
|
|
/// [`free_box`]).
|
|
pub fn oakengine_project_sequence_at(
|
|
self_: *const OakEngineProject,
|
|
index: c_int,
|
|
) -> *mut OakEngineSequence;
|
|
/// `oakengine_project_set_filename`.
|
|
pub fn oakengine_project_set_filename(
|
|
self_: *mut OakEngineProject,
|
|
path: *const c_char,
|
|
) -> c_int;
|
|
/// `oakengine_project_node_count` — parseable content check.
|
|
pub fn oakengine_project_node_count(self_: *const OakEngineProject) -> c_int;
|
|
/// `oakengine_project_node_at` — boxed project node at `index` (free
|
|
/// with `oakengine_node_free`); NULL for an invalid index.
|
|
pub fn oakengine_project_node_at(
|
|
self_: *const OakEngineProject,
|
|
index: c_int,
|
|
) -> *mut OakEngineNode;
|
|
/// `oakengine_project_root` — the project's root folder node (boxed,
|
|
/// free with `oakengine_node_free`).
|
|
pub fn oakengine_project_root(self_: *const OakEngineProject) -> *mut OakEngineNode;
|
|
|
|
/// `oakengine_folder_item_child_count` — direct children of a folder
|
|
/// node.
|
|
pub fn oakengine_folder_item_child_count(folder: *const OakEngineNode) -> c_int;
|
|
/// `oakengine_folder_item_child` — boxed child at `index` (free with
|
|
/// `oakengine_node_free`); NULL for an invalid index.
|
|
pub fn oakengine_folder_item_child(
|
|
folder: *const OakEngineNode,
|
|
index: c_int,
|
|
) -> *mut OakEngineNode;
|
|
/// `oakengine_node_get_name` (buf/size).
|
|
pub fn oakengine_node_get_name(
|
|
self_: *const OakEngineNode,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_node_get_label` (buf/size).
|
|
pub fn oakengine_node_get_label(
|
|
self_: *const OakEngineNode,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_node_input_count`.
|
|
pub fn oakengine_node_input_count(self_: *const OakEngineNode) -> c_int;
|
|
/// `oakengine_node_input_id` (buf/size) at `index`.
|
|
pub fn oakengine_node_input_id(
|
|
self_: *const OakEngineNode,
|
|
index: c_int,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_node_input_is_connected` — 1 when the input has an edge.
|
|
pub fn oakengine_node_input_is_connected(
|
|
self_: *const OakEngineNode,
|
|
input_id: *const c_char,
|
|
) -> c_int;
|
|
/// `oakengine_node_output_connection_count`.
|
|
pub fn oakengine_node_output_connection_count(self_: *const OakEngineNode) -> c_int;
|
|
/// `oakengine_node_output_connection_at_ex` — boxed input node + input
|
|
/// id (free the boxed node with `oakengine_node_free`).
|
|
pub fn oakengine_node_output_connection_at_ex(
|
|
self_: *const OakEngineNode,
|
|
index: c_int,
|
|
input_node: *mut *mut OakEngineNode,
|
|
input_id_buf: *mut c_char,
|
|
input_id_size: c_int,
|
|
element: *mut c_int,
|
|
hidden: *mut c_int,
|
|
) -> c_int;
|
|
/// `oakengine_node_get_context_position` — graph position of `node`
|
|
/// in `context`'s position map.
|
|
pub fn oakengine_node_get_context_position(
|
|
context: *const OakEngineNode,
|
|
node: *const OakEngineNode,
|
|
x: *mut f64,
|
|
y: *mut f64,
|
|
expanded: *mut c_int,
|
|
) -> c_int;
|
|
/// `oakengine_node_set_context_position` — undoable graph move.
|
|
pub fn oakengine_node_set_context_position(
|
|
context: *mut OakEngineNode,
|
|
node: *mut OakEngineNode,
|
|
x: f64,
|
|
y: f64,
|
|
) -> c_int;
|
|
/// `oakengine_node_connect` — undoable edge from `output_node` into
|
|
/// `input_node`'s `input_id`.
|
|
pub fn oakengine_node_connect(
|
|
output_node: *mut OakEngineNode,
|
|
input_node: *mut OakEngineNode,
|
|
input_id: *const c_char,
|
|
) -> c_int;
|
|
/// `oakengine_node_disconnect_ex` — undoable edge removal.
|
|
pub fn oakengine_node_disconnect_ex(
|
|
input_node: *mut OakEngineNode,
|
|
input_id: *const c_char,
|
|
element: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_project_remove_node` — undoable node removal (edges
|
|
/// disconnected).
|
|
pub fn oakengine_project_remove_node(
|
|
self_: *mut OakEngineProject,
|
|
node: *mut OakEngineNode,
|
|
) -> c_int;
|
|
|
|
// -- oakengine::task --
|
|
|
|
/// `oakengine_task_error` (buf/size).
|
|
pub fn oakengine_task_error(
|
|
task: *mut OakEngineTask,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_task_cancel` — set the task's cancel atom.
|
|
pub fn oakengine_task_cancel(task: *mut OakEngineTask) -> c_int;
|
|
/// `oakengine_task_start_sync` — run the task to completion.
|
|
pub fn oakengine_task_start_sync(task: *mut OakEngineTask) -> c_int;
|
|
/// `oakengine_task_free` — consuming free of an owned task box.
|
|
pub fn oakengine_task_free(task: *mut OakEngineTask) -> c_int;
|
|
/// `oakengine_task_create_project_load_otio` — owned interchange load
|
|
/// task box.
|
|
pub fn oakengine_task_create_project_load_otio(filename: *const c_char) -> *mut OakEngineTask;
|
|
/// `oakengine_task_create_project_save_otio` — owned interchange save
|
|
/// task box.
|
|
pub fn oakengine_task_create_project_save_otio(
|
|
project: *mut OakEngineProject,
|
|
) -> *mut OakEngineTask;
|
|
/// `oakengine_task_create_export` — owned export task box.
|
|
pub fn oakengine_task_create_export(
|
|
sequence: *mut OakEngineSequence,
|
|
params: *mut OakEngineEncodingParams,
|
|
) -> *mut OakEngineTask;
|
|
|
|
// -- oakengine::timeline --
|
|
|
|
/// `oakengine_sequence_new` — in-memory sequence (facade scratch
|
|
/// project).
|
|
pub fn oakengine_sequence_new(
|
|
project: *mut OakEngineProject,
|
|
name: *const c_char,
|
|
) -> *mut OakEngineSequence;
|
|
/// `oakengine_sequence_name` (buf/size).
|
|
pub fn oakengine_sequence_name(
|
|
self_: *const OakEngineSequence,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_get_length` — length in seconds.
|
|
pub fn oakengine_sequence_get_length(
|
|
self_: *const OakEngineSequence,
|
|
seconds: *mut f64,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_get_frame_rate` — num/den rational.
|
|
pub fn oakengine_sequence_get_frame_rate(
|
|
self_: *const OakEngineSequence,
|
|
num: *mut c_int,
|
|
den: *mut c_int,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_get_video_params` — width/height/par.
|
|
pub fn oakengine_sequence_get_video_params(
|
|
self_: *const OakEngineSequence,
|
|
width: *mut c_int,
|
|
height: *mut c_int,
|
|
par_num: *mut c_int,
|
|
par_den: *mut c_int,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_track_count` — per-type counts.
|
|
pub fn oakengine_sequence_track_count(
|
|
self_: *const OakEngineSequence,
|
|
video: *mut c_int,
|
|
audio: *mut c_int,
|
|
subtitle: *mut c_int,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_set_playhead`.
|
|
pub fn oakengine_sequence_set_playhead(self_: *mut OakEngineSequence, timestamp: i64) -> c_int;
|
|
/// `oakengine_sequence_add_track`.
|
|
pub fn oakengine_sequence_add_track(self_: *mut OakEngineSequence, track_type: c_int) -> c_int;
|
|
/// `oakengine_sequence_last_error` — last editing error for this
|
|
/// thread (two-stage string).
|
|
pub fn oakengine_sequence_last_error(buf: *mut c_char, buf_size: c_int) -> c_int;
|
|
/// `oakengine_sequence_clip_count`.
|
|
pub fn oakengine_sequence_clip_count(
|
|
self_: *mut OakEngineSequence,
|
|
track_type: c_int,
|
|
track_index: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_clip_at` — borrowed clip box (free with
|
|
/// [`free_box`]).
|
|
pub fn oakengine_sequence_clip_at(
|
|
self_: *mut OakEngineSequence,
|
|
track_type: c_int,
|
|
track_index: c_int,
|
|
clip_index: c_int,
|
|
) -> *mut OakEngineClip;
|
|
/// `oakengine_clip_get_range` — clip timeline range and media in-point
|
|
/// as frame timestamps.
|
|
pub fn oakengine_clip_get_range(
|
|
self_: *const OakEngineClip,
|
|
in_: *mut i64,
|
|
out: *mut i64,
|
|
media_in: *mut i64,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_split_clip`.
|
|
pub fn oakengine_sequence_split_clip(
|
|
seq: *mut OakEngineSequence,
|
|
track_type: c_int,
|
|
track_index: c_int,
|
|
clip_index: c_int,
|
|
time: i64,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_move_clip_to_track` — move the clip to a
|
|
/// different track (M12 P4, cross-track).
|
|
pub fn oakengine_sequence_move_clip_to_track(
|
|
seq: *mut OakEngineSequence,
|
|
track_type: c_int,
|
|
track_index: c_int,
|
|
clip_index: c_int,
|
|
dest_track_index: c_int,
|
|
new_in: i64,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_move_clip` — move the clip so its in point becomes
|
|
/// `new_in` on the same track.
|
|
pub fn oakengine_sequence_move_clip(
|
|
seq: *mut OakEngineSequence,
|
|
track_type: c_int,
|
|
track_index: c_int,
|
|
clip_index: c_int,
|
|
new_in: i64,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_ripple_delete_clip`.
|
|
pub fn oakengine_sequence_ripple_delete_clip(
|
|
seq: *mut OakEngineSequence,
|
|
track_type: c_int,
|
|
track_index: c_int,
|
|
clip_index: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_clip_trim` — change the clip's timeline range.
|
|
pub fn oakengine_clip_trim(clip: *mut OakEngineClip, new_in: i64, new_out: i64) -> c_int;
|
|
/// `oakengine_sequence_delete_clips` — delete a clip array, optionally
|
|
/// rippling; `rippled` reports the ripple length.
|
|
pub fn oakengine_sequence_delete_clips(
|
|
seq: *mut OakEngineSequence,
|
|
clips: *mut *mut OakEngineClip,
|
|
clip_count: c_int,
|
|
ripple: c_int,
|
|
ripple_ranges_ts: *const i64,
|
|
ripple_range_count: c_int,
|
|
rippled: *mut c_int,
|
|
) -> c_int;
|
|
/// `oakengine_sequence_remove_track`.
|
|
pub fn oakengine_sequence_remove_track(
|
|
seq: *mut OakEngineSequence,
|
|
track_type: c_int,
|
|
track_index: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_track_get_height` — height in internal units.
|
|
pub fn oakengine_track_get_height(
|
|
seq: *const OakEngineSequence,
|
|
track_type: c_int,
|
|
track_index: c_int,
|
|
height: *mut f64,
|
|
) -> c_int;
|
|
/// `oakengine_track_set_height` — height in internal units.
|
|
pub fn oakengine_track_set_height(
|
|
seq: *mut OakEngineSequence,
|
|
track_type: c_int,
|
|
track_index: c_int,
|
|
height: f64,
|
|
) -> c_int;
|
|
|
|
/// `oakengine_track_height_internal_to_pixels`.
|
|
pub fn oakengine_track_height_internal_to_pixels(height: f64) -> c_int;
|
|
/// `oakengine_track_height_pixels_to_internal`.
|
|
pub fn oakengine_track_height_pixels_to_internal(pixels: c_int) -> f64;
|
|
|
|
// -- oakengine::node (effect chain) --
|
|
//
|
|
// The effect-stack surface over a selected clip: convert the clip to its
|
|
// node view, enumerate its effect chain (index 0 = closest to the
|
|
// source), and edit the chain (insert / remove / reorder / enable
|
|
// toggle), each edit packaged as an undoable facade command. Factory
|
|
// enumeration feeds the "add effect" menu. Node boxes are freed with
|
|
// `oakengine_node_free`; the clip box returned by
|
|
// `oakengine_sequence_clip_at` is freed with [`free_box`].
|
|
|
|
/// `oakengine_clip_as_node` — the clip's node view (borrowed box;
|
|
/// freed with `oakengine_node_free`).
|
|
pub fn oakengine_clip_as_node(self_: *const OakEngineClip) -> *mut OakEngineNode;
|
|
/// `oakengine_sequence_as_node` — the sequence's node view (borrowed
|
|
/// box; freed with `oakengine_node_free`). The node editor's graph
|
|
/// output node and the context for its position map.
|
|
pub fn oakengine_sequence_as_node(self_: *const OakEngineSequence) -> *mut OakEngineNode;
|
|
/// `oakengine_sequence_node_count` — nodes in the sequence's owning
|
|
/// project (its graph; 0 for NULL/invalid).
|
|
pub fn oakengine_sequence_node_count(self_: *const OakEngineSequence) -> c_int;
|
|
/// `oakengine_sequence_node_at` — boxed graph node at `index` (freed
|
|
/// with `oakengine_node_free`); NULL for an invalid index or sequence.
|
|
pub fn oakengine_sequence_node_at(
|
|
self_: *const OakEngineSequence,
|
|
index: c_int,
|
|
) -> *mut OakEngineNode;
|
|
/// `oakengine_sequence_remove_node` — undoable removal of `node` from
|
|
/// the sequence's graph (the sequence node itself is protected).
|
|
pub fn oakengine_sequence_remove_node(
|
|
self_: *mut OakEngineSequence,
|
|
node: *mut OakEngineNode,
|
|
) -> c_int;
|
|
/// `oakengine_clip_get_media_filename` — the clip's upstream footage
|
|
/// filename (two-stage buf/size; M12 P4).
|
|
pub fn oakengine_clip_get_media_filename(
|
|
self_: *const OakEngineClip,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_waveform_extract` — real waveform extraction (M12 P4):
|
|
/// two-stage min/max extraction of `filename`'s audio stream.
|
|
pub fn oakengine_waveform_extract(
|
|
filename: *const c_char,
|
|
stream_index: c_int,
|
|
samples_per_point: c_int,
|
|
out_pairs: *mut crate::oakui::waveform::MinMax,
|
|
capacity_points: c_int,
|
|
out_channel_count: *mut c_int,
|
|
) -> c_int;
|
|
|
|
|
|
/// `oakengine_node_effect_count` — the chain length (0 without effects).
|
|
pub fn oakengine_node_effect_count(self_: *const OakEngineNode) -> c_int;
|
|
/// `oakengine_node_effect_at` — the `index`-th effect (borrowed box;
|
|
/// NULL out of range / no chain).
|
|
pub fn oakengine_node_effect_at(
|
|
self_: *const OakEngineNode,
|
|
index: c_int,
|
|
) -> *mut OakEngineNode;
|
|
/// `oakengine_node_identity` — the node's stable identity (the stack's
|
|
/// card id). 0 for NULL/invalid.
|
|
pub fn oakengine_node_identity(self_: *const OakEngineNode) -> u64;
|
|
/// `oakengine_node_is_enabled` — 1/0 (the stack's enable switch).
|
|
pub fn oakengine_node_is_enabled(self_: *const OakEngineNode) -> c_int;
|
|
/// `oakengine_node_effect_set_enabled` — undoable enable toggle.
|
|
pub fn oakengine_node_effect_set_enabled(self_: *mut OakEngineNode, enabled: c_int) -> c_int;
|
|
/// `oakengine_node_effect_insert` — undoable insert at `index`
|
|
/// (0 = closest to the source; clamped to the ends).
|
|
pub fn oakengine_node_effect_insert(
|
|
self_: *mut OakEngineNode,
|
|
index: c_int,
|
|
type_id: *const c_char,
|
|
) -> c_int;
|
|
/// `oakengine_node_effect_remove` — undoable removal of `effect` from
|
|
/// `self_`'s chain (the node is left orphaned in the project graph).
|
|
pub fn oakengine_node_effect_remove(
|
|
self_: *mut OakEngineNode,
|
|
effect: *mut OakEngineNode,
|
|
) -> c_int;
|
|
/// `oakengine_node_effect_move` — undoable reorder of `effect` to
|
|
/// `new_index` (post-removal insertion index, matching the effect
|
|
/// stack's `ReorderRequested`).
|
|
pub fn oakengine_node_effect_move(
|
|
self_: *mut OakEngineNode,
|
|
effect: *mut OakEngineNode,
|
|
new_index: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_node_get_effect_input` — the id of the input the effect
|
|
/// chain attaches to (negative error when the node cannot host
|
|
/// effects; `element` receives -1 for non-array inputs).
|
|
pub fn oakengine_node_get_effect_input(
|
|
self_: *const OakEngineNode,
|
|
input_id: *mut c_char,
|
|
input_id_size: c_int,
|
|
element: *mut c_int,
|
|
) -> c_int;
|
|
/// `oakengine_node_get_type_id` — the node's factory type id (buf/size).
|
|
pub fn oakengine_node_get_type_id(
|
|
self_: *const OakEngineNode,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_node_get_flags` — the node's flags bitmask (0 for
|
|
/// NULL/invalid).
|
|
pub fn oakengine_node_get_flags(self_: *const OakEngineNode) -> u64;
|
|
/// `oakengine_node_free` — free a node box (NULL no-op).
|
|
pub fn oakengine_node_free(node: *mut OakEngineNode);
|
|
/// `oakengine_node_factory_id_count` — factory entry count.
|
|
pub fn oakengine_node_factory_id_count() -> c_int;
|
|
/// `oakengine_node_factory_id_at` — type id at `index` (buf/size;
|
|
/// negative error out of range).
|
|
pub fn oakengine_node_factory_id_at(index: c_int, buf: *mut c_char, buf_size: c_int) -> c_int;
|
|
/// `oakengine_node_factory_name_from_id` — display name (buf/size).
|
|
pub fn oakengine_node_factory_name_from_id(
|
|
type_id: *const c_char,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_node_factory_create_from_id` — owned node, not added.
|
|
pub fn oakengine_node_factory_create_from_id(type_id: *const c_char) -> *mut OakEngineNode;
|
|
/// `oakengine_node_flag_video_effect`.
|
|
pub fn oakengine_node_flag_video_effect() -> u64;
|
|
/// `oakengine_node_flag_dont_show_in_create_menu`.
|
|
pub fn oakengine_node_flag_dont_show_in_create_menu() -> u64;
|
|
|
|
// -- oakengine::render (CPU frame renderer) --
|
|
//
|
|
// The renderer binds a sequence handle to an output geometry; each
|
|
// `render_frame` submits an oakrender ticket, waits for it and returns
|
|
// the produced frame (F32 RGBA when created with pixel format 4). The
|
|
// renderer box is opaque: it is freed ONLY with
|
|
// `oakengine_renderer_free` (never [`free_box`] — the facade box is not
|
|
// a module-handle box). Frames are freed with `oakengine_frame_free`.
|
|
|
|
/// `oakengine_renderer_create` — owned renderer box (NULL on invalid
|
|
/// arguments: NULL sequence, non-positive geometry/rate, or a pixel
|
|
/// format outside 0..=4). `output_colorspace` may be NULL.
|
|
pub fn oakengine_renderer_create(
|
|
seq: *mut OakEngineSequence,
|
|
width: c_int,
|
|
height: c_int,
|
|
pixel_format: c_int,
|
|
frame_rate_num: c_int,
|
|
frame_rate_den: c_int,
|
|
output_colorspace: *const c_char,
|
|
) -> *mut OakEngineRenderer;
|
|
/// `oakengine_renderer_create_for_node` — like `oakengine_renderer_create`,
|
|
/// but binds any node instead of a sequence: the surface for rendering a
|
|
/// single footage node (the source monitor). Freed and rendered exactly
|
|
/// like the sequence renderer.
|
|
pub fn oakengine_renderer_create_for_node(
|
|
node: *mut OakEngineNode,
|
|
width: c_int,
|
|
height: c_int,
|
|
pixel_format: c_int,
|
|
frame_rate_num: c_int,
|
|
frame_rate_den: c_int,
|
|
output_colorspace: *const c_char,
|
|
) -> *mut OakEngineRenderer;
|
|
/// `oakengine_renderer_free` — consuming free (NULL no-op).
|
|
pub fn oakengine_renderer_free(self_: *mut OakEngineRenderer);
|
|
/// `oakengine_renderer_render_frame` — synchronous render of the frame at
|
|
/// `timestamp` (in the renderer's frame-rate units). NULL on failure
|
|
/// (see `oakengine_renderer_last_error`).
|
|
pub fn oakengine_renderer_render_frame(
|
|
self_: *mut OakEngineRenderer,
|
|
timestamp: i64,
|
|
) -> *mut OakEngineFrame;
|
|
/// `oakengine_renderer_last_error` (buf/size).
|
|
pub fn oakengine_renderer_last_error(
|
|
self_: *const OakEngineRenderer,
|
|
buf: *mut c_char,
|
|
buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_renderer_render_audio` — synchronously render
|
|
/// `length_timestamp` frames of audio starting at `start_timestamp`
|
|
/// (M12 P1). Owned `OakEngineAudioBuffer`.
|
|
pub fn oakengine_renderer_render_audio(
|
|
self_: *mut OakEngineRenderer,
|
|
start_timestamp: i64,
|
|
length_timestamp: i64,
|
|
) -> *mut OakEngineAudioBuffer;
|
|
/// `oakengine_audio_sample_rate` — the rendered buffer's rate (Hz).
|
|
pub fn oakengine_audio_sample_rate(self_: *const OakEngineAudioBuffer) -> c_int;
|
|
/// `oakengine_audio_channel_count`.
|
|
pub fn oakengine_audio_channel_count(self_: *const OakEngineAudioBuffer) -> c_int;
|
|
/// `oakengine_audio_sample_count` — interleaved frame count.
|
|
pub fn oakengine_audio_sample_count(self_: *const OakEngineAudioBuffer) -> i64;
|
|
/// `oakengine_audio_data` — interleaved f32 samples.
|
|
pub fn oakengine_audio_data(
|
|
self_: *const OakEngineAudioBuffer,
|
|
channel: c_int,
|
|
) -> *const f32;
|
|
/// `oakengine_audio_free` — release the buffer.
|
|
pub fn oakengine_audio_free(self_: *mut OakEngineAudioBuffer);
|
|
/// `oakengine_audio_push_to_output` — queue interleaved samples
|
|
/// described by `params` for playback (M12 P1).
|
|
pub fn oakengine_audio_push_to_output(
|
|
params: *const c_void,
|
|
samples: *const c_char,
|
|
samples_size: i64,
|
|
error_buf: *mut c_char,
|
|
error_buf_size: c_int,
|
|
) -> c_int;
|
|
/// `oakcore_audioparams_create` — new owned audio params (release
|
|
/// with `oakcore_audioparams_free`).
|
|
pub fn oakcore_audioparams_create(
|
|
sample_rate: c_int,
|
|
channel_layout: u64,
|
|
format: c_int,
|
|
) -> *mut c_void;
|
|
/// `oakcore_audioparams_free` (NULL no-op).
|
|
pub fn oakcore_audioparams_free(params: *mut c_void);
|
|
/// `oakengine_render_manager_shutdown` — tear down the render manager
|
|
/// (test/tooling; the app keeps it for the process lifetime).
|
|
pub fn oakengine_render_manager_shutdown() -> c_int;
|
|
/// `oakengine_testmedia_write_clip` — encode the known test pattern
|
|
/// into `path` (test/tooling only; M12 P0).
|
|
pub fn oakengine_testmedia_write_clip(
|
|
path: *const c_char,
|
|
width: c_int,
|
|
height: c_int,
|
|
frame_count: c_int,
|
|
fps: c_int,
|
|
) -> c_int;
|
|
/// `oakengine_frame_width`.
|
|
pub fn oakengine_frame_width(self_: *const OakEngineFrame) -> c_int;
|
|
/// `oakengine_frame_height`.
|
|
pub fn oakengine_frame_height(self_: *const OakEngineFrame) -> c_int;
|
|
/// `oakengine_frame_format` — `PixelFormat::Format` (4 = F32).
|
|
pub fn oakengine_frame_format(self_: *const OakEngineFrame) -> c_int;
|
|
/// `oakengine_frame_linesize_bytes` — row stride in bytes.
|
|
pub fn oakengine_frame_linesize_bytes(self_: *const OakEngineFrame) -> c_int;
|
|
/// `oakengine_frame_data` — borrowed pixel data (valid until free).
|
|
pub fn oakengine_frame_data(self_: *const OakEngineFrame) -> *const c_void;
|
|
/// `oakengine_frame_free` — consuming free (NULL no-op).
|
|
pub fn oakengine_frame_free(self_: *mut OakEngineFrame);
|
|
|
|
// -- oakengine::audio --
|
|
|
|
/// `oakengine_audio_output_levels` — per-channel linear peaks of the
|
|
/// buffered output into `peaks` (up to `capacity` entries); returns
|
|
/// the channel count (0 = nothing buffered), negative on error.
|
|
pub fn oakengine_audio_output_levels(peaks: *mut f32, capacity: c_int) -> c_int;
|
|
|
|
// -- oakengine::render (manager lifecycle) --
|
|
|
|
/// `oakengine_render_manager_init` — bring up the module's
|
|
/// process-global render manager. Without it `render_frame` fails with
|
|
/// NULL + last_error. Fails (nonzero) when the manager is already
|
|
/// initialized.
|
|
pub fn oakengine_render_manager_init() -> c_int;
|
|
/// `oakengine_render_manager_available` — 1 when the render manager is up.
|
|
pub fn oakengine_render_manager_available() -> c_int;
|
|
|
|
// -- oakengine::task (interchange load result + event subscription) --
|
|
|
|
/// `oakengine_task_load_take_project` — take the project an
|
|
/// interchange load/load-otio task produced (ownership moves to the
|
|
/// caller; release with `oakengine_project_free`). NULL when the task
|
|
/// is not a load task or has no project yet.
|
|
pub fn oakengine_task_load_take_project(task: *mut OakEngineTask) -> *mut OakEngineProject;
|
|
/// `oakengine_task_subscribe` — register the task event callback
|
|
/// (`OAKTASK_EVENT_STARTED`=0, `OAKTASK_EVENT_PROGRESS`=1,
|
|
/// `OAKTASK_EVENT_FINISHED`=2).
|
|
pub fn oakengine_task_subscribe(
|
|
task: *mut OakEngineTask,
|
|
cb: Option<OakTaskEventFn>,
|
|
userdata: *mut c_void,
|
|
) -> i64;
|
|
}
|
|
|
|
/// The C-ABI `MinMax` mirror (exported for the integration tests).
|
|
#[repr(C)]
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct oakapp_minmax {
|
|
/// Minimum amplitude.
|
|
pub min: f32,
|
|
/// Maximum amplitude.
|
|
pub max: f32,
|
|
}
|