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)
103 lines
5.0 KiB
Markdown
103 lines
5.0 KiB
Markdown
# oakaudio Rust crate
|
|
|
|
> Status: **implemented**. The C ABI (`include/audio/*.h`) is implemented
|
|
> by `src/ffi.rs`; the contract suite lives in `tests/` (all green,
|
|
> ~88% line coverage under tarpaulin). The architecture below mirrors the
|
|
> oaknode/oakrender crate template (FFI discipline, testing layers) from
|
|
> `crates/oaknode/README.md` and `crates/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)
|
|
|
|
1. **Singleton manager.** `AudioManager` is a process-wide PortAudio
|
|
singleton. Rust keeps the singleton behind a `OnceLock<Mutex<...>>`
|
|
with borrow-only handles: `addref`/`release` are no-ops exactly as on
|
|
the C++ side, and an empty handle reports `OAKAUDIO_E_STATE`. No
|
|
destruction ever happens through the handle.
|
|
2. **Processor is the only heavy FFI consumer.** `AudioProcessor` wraps
|
|
the ffmpeg_bridge audio filter graph (`fb_audio_graph_*`,
|
|
`fb_frame_*`); every call funnels through `bridge::ffmpeg`. The
|
|
resampler/format-conversion semantics and the always-planar-f32
|
|
output (`OAKAUDIO_PROCESSOR_OUTPUT_FORMAT = 4`) are preserved.
|
|
3. **Sync helpers are stateless.** `AudioSynchronizer` and
|
|
`AudioWaveformSync` have only static methods in C++; they become
|
|
plain functions in `synchronizer.rs` / `waveformsync.rs`. No handles
|
|
are involved on the sync headers except by-value arguments.
|
|
4. **Value types are local.** `params.rs` defines `AudioParams` (a
|
|
plain POD) and a **planar-first** `SampleFormat` enum mirroring
|
|
`olive::core::SampleFormat::Format` exactly, because these values
|
|
cross the C ABI as `int`. See the note in `params.rs` about why the
|
|
crate does not reuse `oakcore-rs`'s `SampleFormat`.
|
|
5. **Rational reuses oakcore-rs.** `core::Rational` (used by
|
|
synchronizer and waveform) comes from `oakcore-rs`; there is no
|
|
local copy.
|
|
6. **Record path through oakcodec.** `AudioManager` records 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.
|
|
7. **Config via oakcommon.** Device names and the output buffer size
|
|
read through `bridge::common` (`oakcommon_config_*`), preserving the
|
|
`audio_config` namespace semantics as a `config.rs` free-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
|
|
|
|
1. Every `extern "C"` body goes through `handle::guard*`; no panic
|
|
crosses FFI. The manager's borrow-only singleton is the one place
|
|
`guard_handle`/`guard_void` are used with no refcount semantics.
|
|
2. `SampleFormat` and `AudioParams` integer values MUST match the C++
|
|
enums bit-for-bit; `// CPP-PARITY:` comments mark every load-bearing
|
|
layout decision.
|
|
3. Behavior parity with C++ is proven by the C ABI test-suite
|
|
(`src/audio/tests`, unchanged) plus the golden tests in `tests/`
|
|
(waveform mipmap/channel-interleaved layout, RMS/LUFS thresholds,
|
|
sync placement).
|
|
4. 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.
|
|
5. `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.
|