Initial pure C++ implementation
ci / ubuntu-latest (push) Failing after 13s
ci / macos-latest (push) Has been cancelled
ci / windows-latest (push) Has been cancelled

This commit is contained in:
2026-09-25 04:17:30 +08:00
commit 42d6da6302
110 changed files with 37510 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
# JustOneCacophony — C++ Core
[中文](README.md) · [Mathematics](docs/math.en.md) · [Binaural rendering](docs/binaural.en.md) · [SIMD dispatch](docs/simd.en.md)
The C++ implementation of JustOneCacophony: an execution core for E-AC-3 JOC
bitstream parsing, object reconstruction and rendering. It extracts EMDF, ID14 JOC
parameters and ID11 OAMD metadata from E-AC-3 syncframes, combines them with the
core 5.1 PCM decoded by FFmpeg to rebuild the LFE and 15 object signals, and writes
ADM BWF, a WAV for a chosen speaker layout, or a binaural WAV using a compiled HRTF
directional field.
This is research code, not a complete, standard-conformant or production JOC
decoder. It covers the bitstream forms it implements and reports an explicit error
on unknown variants instead of pretending everything is in harmony.
## Building
```powershell
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure
```
Only MSVC (VS 2022, static CRT) is validated locally; Linux and macOS are built and
unit-tested by `.github/workflows/ci.yml`. Floating-point behaviour is part of the
byte-exact acceptance, so fast-math is never enabled: `/fp:precise` on MSVC,
`-fno-fast-math` elsewhere.
Windows Release builds target AVX2 by default (`JOC_ENABLE_AVX2`, ON, see
`CMakeLists.txt`). That switch is itself part of the byte-exact acceptance — the
SHA-256 of every rendered output is identical — and buys 12.4% on the SOFA binaural
kernel and 1.8% on Rosella. The price is a runtime requirement: such a `joc_core.dll`
executes AVX2 instructions and dies on an illegal instruction on pre-2013 x86. There
is no runtime dispatch, so a binary is one or the other:
```powershell
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DJOC_ENABLE_AVX2=OFF
```
gives a baseline-ISA (SSE2) build that runs on any x86-64. Non-MSVC builds never
receive the flag.
### Paths and encoding
Every path inside the library is **UTF-8**, converted only at the OS boundary
(`src/foundation/fs_utf8.*`): on Windows through `std::filesystem::path` (UTF-16
inside) into `_wfopen`/`CreateProcessW`, and as plain bytes elsewhere. Command line
arguments are re-parsed from `GetCommandLineW` + `CommandLineToArgvW` on Windows and
the console code page is set to UTF-8, so non-ASCII paths (Japanese, Chinese, ...)
work for the input, the ffmpeg child process and the output files alike; a
non-ASCII path regression case runs in `ctest`.
## Artifacts
| Artifact | Purpose |
|---|---|
| `joc_core.dll` | The engine: bitstream parsing, JOC/OAMD, DSP, speaker and binaural rendering, ADM BWF/WAV writing, file task, streaming surface |
| `joc_cli.exe` | Command line frontend for file tasks |
| `include/joc_core.h` | Engine, telemetry and file-task interface (pure C) |
| `include/joc_stream.h` | Embedder-facing streaming push/pull interface (pure C, self-contained) |
## Command line
The arguments match the reference Python CLI exactly: the input is positional,
**ADM BWF is the default output**, `--speaker-layout` or `--binaural` selects the
other two modes, and without `-o` the result lands in `output/`.
```powershell
# Default: ADM BWF (inherently 24-bit, so there is no format option)
joc_cli "07. Gold Forever (2021 Master).m4a"
# -> output/07. Gold Forever (2021 Master).adm.wav
joc_cli input.m4a -o out/adm.wav # explicit output
# Speaker layout
joc_cli input.m4a --speaker-layout 5.1 # -> output/<name>.5.1.wav
joc_cli input.m4a --speaker-layout 7.1.4 --speaker-output out/714.wav --speaker-format int24
# Binaural (HRTF defaults to <exe>/HRTF/binaural.sofa, then <exe>/HRTF/binaural.personalized_headphone)
joc_cli input.m4a --binaural
joc_cli input.m4a --binaural --sofa-hrtf HRTF/other.sofa # another SOFA
joc_cli input.m4a --binaural --personalized-headphone # Rosella personalisation
# -> output/<name>.binaural.wav
# Other common switches
joc_cli input.m4a --duration 30 --gain-db -3 --trajectory-mode dense64
joc_cli input.eac3 --metadata-only --print-metadata summary # parse and print metadata only
```
`--speaker-format` / `--binaural-format` default to `float32`; an `int24` request that
would clip follows `--clip-action` (default `ask`; a non-interactive terminal must
pass `continue`, `float32` or `abort`). `--duration` is in **seconds**, and
`--object-delay-samples`, `--speaker-metadata-offset`, `--binaural-tail-seconds` and
`--binaural-tail-threshold` (1e-8, the binaural tail trim) keep the reference
defaults. A run always writes `<output>.report.json` (`--report-json` overrides it).
**Differences from the reference:** `--sofa-hrtf`, `--personalized-headphone`,
`--backend python` and the metadata sidecars (`--metadata-dir`, `--metadata-cache`,
`--metadata-backend sidecar`) are unavailable in this build and fail immediately
with an explanation instead of being ignored. This build adds `--bed` (pre-decoded
6-channel float32 PCM, which skips ffmpeg decoding), `--kernels`, `--work-dir`,
`--report-json`, `--dry-run` and `--quiet`.
## Library integration
The engine and the file task are exposed by `joc_core.h`: `joc_task_validate` /
`joc_task_execute` run a file task and report state events through a callback, and
`joc_task_result` carries frame counts, peak, byte count and SHA-256.
Players and decoder components use `joc_stream.h`: the caller pushes E-AC-3 bytes
and the matching core PCM (or already-rebuilt objects16) at its own pace and pulls
rendered PCM. Any chunking is allowed, and the result is byte-identical to the file
task.
```c
joc_stream_config config = {0};
config.struct_size = sizeof(config);
config.input = JOC_STREAM_IN_EAC3; /* or JOC_STREAM_IN_PCM_OBJECTS16 */
config.output = JOC_STREAM_OUT_SPEAKER; /* or BINAURAL / PCM_OBJECTS16 */
config.speaker_layout_name = "5.1";
joc_stream* stream = NULL;
joc_stream_create(&config, &stream);
/* loop: joc_stream_push(...) / joc_stream_pull(...) */
joc_stream_flush(stream);
joc_stream_destroy(stream);
```
Contract: state is instance-private, so streams coexist; push and pull on one
instance must come from the same thread; rendering is stateful, so **this version
offers no seek** - repositioning means decoding from the start of the stream. The
kernel latency is 961 samples for speaker/binaural output and `joc_stream_flush`
drains the binaural room tail.
## Layout
```text
include/ public C ABI: joc_core.h (engine/file task), joc_stream.h (streaming),
eac3joc_core.h (upstream ABI)
src/ implementation: eac3_transport, emdf, joc_bitstream, joc_core, oamd,
timeline, speaker, binaural, hrtf, adm, io, telemetry, task, stream,
api, cli, simd
tests/ unit tests (CTest, self-contained, no external data)
docs/ mathematics, the binaural rendering flow and SIMD dispatch
```
Eight files under `src/` are byte-identical copies of the upstream JustOneCacophony
native library and are never edited (see [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)).
## Compatibility note
`object_delay_samples` defaults to **1473**, preserving the behaviour of the existing
implementation; it is a configurable field and changing it changes the OAMD/ADM time
alignment. Upstream investigation suggests the value should be 0; this project keeps
the current default to stay byte-identical.
## License
MIT, see [LICENSE](LICENSE). Third-party provenance and patent boundaries are in
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).