Single mechanical restructure commit: - root Cargo.toml = oakapp bin + workspace; one cargo build produces oakapp, oak-cli, oak-worker, liboakengine.dylib - app/rust/src -> src/ (app at repo root, no rust/ nesting) - src/<mod>/rust -> crates/oak<mod>; src/oakcore-rs -> crates/oakcore; src/bindings/oakotio -> crates/oakotio; src/engine/rust -> crates/oakengine (keeps cdylib+staticlib+rlib) - public C headers include/<mod>/ -> crates/oakengine/include/<mod>/ - OFX SDK headers vendored into crates/oakplugin/ofx/ (HostSupport gone) - legacy deleted: old src/ C++ modules, engine/, core/, ffmpeg_bridge/, app/ (Qt), cli/worker C++, root CMakeLists, third_party/KDDockWidgets submodule, otio-install, all build-* output (~40GB) - oakstorage kept but excluded from the workspace (skeleton w/ todos); gpui excluded (own workspace) - verified: cargo build green, cargo test --workspace 1845/0 (with the documented OCIO_RS_* env override for the homebrew OCIO)
5.0 KiB
oakaudio Rust crate
Status: implemented. The C ABI (
include/audio/*.h) is implemented bysrc/ffi.rs; the contract suite lives intests/(all green, ~88% line coverage under tarpaulin). The architecture below mirrors the oaknode/oakrender crate template (FFI discipline, testing layers) fromcrates/oaknode/README.mdandcrates/oakrender/README.md.
Scope
Replaces the C++ oakaudio module (src/audio/src, ~50k lines): the
PortAudio output/input manager (AudioManager), the real-time
resampler/format converter (AudioProcessor), timeline synchronization
helpers (AudioSynchronizer, AudioWaveformSync), the level meter
(AudioLevelMeter), the visual waveform store (AudioVisualWaveform),
the header-only pull buffer (PreviewAudioDevice), and the config
bridge (audio_config namespace).
Public contract: include/audio/*.h (5 headers plus error.h, ~45
functions) — frozen, implemented verbatim by src/ffi.rs.
Key architectural decisions (C++ → Rust mapping)
- Singleton manager.
AudioManageris a process-wide PortAudio singleton. Rust keeps the singleton behind aOnceLock<Mutex<...>>with borrow-only handles:addref/releaseare no-ops exactly as on the C++ side, and an empty handle reportsOAKAUDIO_E_STATE. No destruction ever happens through the handle. - Processor is the only heavy FFI consumer.
AudioProcessorwraps the ffmpeg_bridge audio filter graph (fb_audio_graph_*,fb_frame_*); every call funnels throughbridge::ffmpeg. The resampler/format-conversion semantics and the always-planar-f32 output (OAKAUDIO_PROCESSOR_OUTPUT_FORMAT = 4) are preserved. - Sync helpers are stateless.
AudioSynchronizerandAudioWaveformSynchave only static methods in C++; they become plain functions insynchronizer.rs/waveformsync.rs. No handles are involved on the sync headers except by-value arguments. - Value types are local.
params.rsdefinesAudioParams(a plain POD) and a planar-firstSampleFormatenum mirroringolive::core::SampleFormat::Formatexactly, because these values cross the C ABI asint. See the note inparams.rsabout why the crate does not reuseoakcore-rs'sSampleFormat. - Rational reuses oakcore-rs.
core::Rational(used by synchronizer and waveform) comes fromoakcore-rs; there is no local copy. - Record path through oakcodec.
AudioManagerrecords through the oakcodec encoder C ABI (bridge::codec) and waveform extraction decodes through the oakcodec decoder C ABI — exactly as the C++ does. No direct ffmpeg_bridge use in the record path. - Config via oakcommon. Device names and the output buffer size
read through
bridge::common(oakcommon_config_*), preserving theaudio_confignamespace semantics as aconfig.rsfree-function module.
Layout
COVERAGE.md maps every C++ audio class/method to its Rust home.
Review that first.
src/
lib.rs crate doc + module map
error.rs error codes (mirrors include/audio/error.h)
handle.rs refcounted-handle scaffolding (same pattern as node)
params.rs AudioParams + planar-first SampleFormat value types
config.rs audio_config namespace (bridge::common)
manager.rs AudioManager singleton (PortAudio I/O, recording)
processor.rs AudioProcessor (resampler/converter, bridge::ffmpeg)
synchronizer.rs AudioSynchronizer placement helpers
levelmeter.rs AudioLevelMeter peak/RMS/VU/LUFS analysis
waveform.rs AudioVisualWaveform mipmapped store + extraction
waveformsync.rs AudioWaveformSync envelope offset estimation
previewdevice.rs PreviewAudioDevice pull buffer
bridge/ C ABI imports: common.rs, codec.rs, ffmpeg.rs
ffi.rs include/audio/*.h export layer
tests/ contract + golden tests (see README test section)
Hard rules for the implementer
- Every
extern "C"body goes throughhandle::guard*; no panic crosses FFI. The manager's borrow-only singleton is the one placeguard_handle/guard_voidare used with no refcount semantics. SampleFormatandAudioParamsinteger values MUST match the C++ enums bit-for-bit;// CPP-PARITY:comments mark every load-bearing layout decision.- Behavior parity with C++ is proven by the C ABI test-suite
(
src/audio/tests, unchanged) plus the golden tests intests/(waveform mipmap/channel-interleaved layout, RMS/LUFS thresholds, sync placement). - Where C++ behavior is genuinely load-bearing but ugly, port the
behavior, not the aesthetics; leave a
// CPP-PARITY:comment with the C++ file:line. src/plugin/is frozen and out of scope; no oakaudio code reaches into it.
Dependency policy
Prefer mature third-party crates (MIT/Apache-2.0/BSD, GPL-compatible) over hand-rolling; register each addition (name + reason) here. Large existing C++ libraries (OTIO, OCIO, OIIO, FFmpeg) are NEVER rewritten — they are consumed through their C ABI / bridge layers.