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
+235
View File
@@ -0,0 +1,235 @@
#pragma once
#include <stdint.h>
#if defined(_WIN32)
#if defined(EJOC_BUILD_DLL)
#define EJOC_API __declspec(dllexport)
#else
#define EJOC_API __declspec(dllimport)
#endif
#define EJOC_CALL __cdecl
#else
#define EJOC_API __attribute__((visibility("default")))
#define EJOC_CALL
#endif
#ifdef __cplusplus
extern "C" {
#endif
enum {
EJOC_ABI_VERSION = 1,
EJOC_FRAME_SAMPLES = 1536,
EJOC_TIMESLOTS = 24,
EJOC_SUBBANDS = 64,
EJOC_CORE_CHANNELS = 5,
EJOC_OUTPUT_CHANNELS = 16,
EJOC_MAX_OBJECTS = 15,
EJOC_MAX_DPOINTS = 2,
EJOC_MAX_PARAMETER_BANDS = 23,
EJOC_SPEAKER_BLOCK_SAMPLES = 32,
EJOC_SPEAKER_COORDINATES = 3,
EJOC_BINAURAL_BLOCK_SAMPLES = 512,
EJOC_BINAURAL_INPUT_CHANNELS = 16,
EJOC_BINAURAL_OUTPUT_CHANNELS = 2,
EJOC_BINAURAL_QMF_BANDS = 64,
EJOC_BINAURAL_HYBRID_BANDS = 77
};
typedef void* ejoc_renderer_handle;
typedef void* ejoc_speaker_renderer_handle;
typedef void* ejoc_binaural_renderer_handle;
/*
Fixed array layouts used by ejoc_renderer_process():
bed5_planar [5][1536]
lfe [1536] or NULL
n_bands [15]
n_dpoints [15]
slope_idx [15]
offset_ts [15][2]
dq [15][2][5][23]
output16 [16][1536]
Only objects selected by object_mask are read from the descriptor arrays.
dq carries already dequantized matrix coefficients in double precision; the
caller performs the JOC bitstream differential decoding for both dense and
sparse objects, so this ABI is identical for both syntaxes.
*/
EJOC_API uint32_t EJOC_CALL ejoc_abi_version(void);
EJOC_API const char* EJOC_CALL ejoc_build_info(void);
EJOC_API ejoc_renderer_handle EJOC_CALL ejoc_renderer_create(void);
EJOC_API void EJOC_CALL ejoc_renderer_destroy(ejoc_renderer_handle handle);
EJOC_API int EJOC_CALL ejoc_renderer_reset(ejoc_renderer_handle handle);
EJOC_API int EJOC_CALL ejoc_renderer_set_threads(ejoc_renderer_handle handle, uint32_t total_threads);
EJOC_API uint32_t EJOC_CALL ejoc_renderer_thread_count(ejoc_renderer_handle handle);
EJOC_API const char* EJOC_CALL ejoc_renderer_last_error(ejoc_renderer_handle handle);
EJOC_API int EJOC_CALL ejoc_renderer_process(
ejoc_renderer_handle handle,
const float* bed5_planar,
const float* lfe,
uint32_t object_mask,
const uint8_t* n_bands,
const uint8_t* n_dpoints,
const uint8_t* slope_idx,
const uint8_t* offset_ts,
const double* dq,
double clipgain,
float phase_new,
float output_scale,
float* output16_planar);
/*
High-precision object-to-speaker renderer.
The renderer consumes interleaved float32 input PCM arranged as:
objects16_interleaved [sample_count][16]
where channel 0 is LFE and channels 1..15 are point objects. All spatial
calculations, gain ramps, and accumulation use double. Output is interleaved:
output_interleaved [sample_count][layout_channel_count]
Each metadata entry is a complete object-state snapshot:
metadata_offsets [metadata_count], relative to this process call
ramp_durations [metadata_count], in samples
positions_q15 [metadata_count][15][3]
region_indices [metadata_count][15] or NULL (all region 0)
height_enabled [metadata_count][15] or NULL (all enabled)
object_gains [metadata_count][15] or NULL (all 1.0)
metadata_offsets must be nondecreasing and <= sample_count. sample_count must
be a multiple of EJOC_SPEAKER_BLOCK_SAMPLES. State and unfinished ramps are
preserved across calls.
*/
EJOC_API uint32_t EJOC_CALL ejoc_speaker_layout_channel_count(uint32_t speaker_bitfield);
EJOC_API ejoc_speaker_renderer_handle EJOC_CALL ejoc_speaker_renderer_create(uint32_t speaker_bitfield);
EJOC_API void EJOC_CALL ejoc_speaker_renderer_destroy(ejoc_speaker_renderer_handle handle);
EJOC_API int EJOC_CALL ejoc_speaker_renderer_reset(ejoc_speaker_renderer_handle handle);
EJOC_API const char* EJOC_CALL ejoc_speaker_renderer_last_error(ejoc_speaker_renderer_handle handle);
EJOC_API int EJOC_CALL ejoc_speaker_renderer_process(
ejoc_speaker_renderer_handle handle,
const float* objects16_interleaved,
uint32_t sample_count,
uint32_t metadata_count,
const uint32_t* metadata_offsets,
const uint32_t* ramp_durations,
const uint16_t* positions_q15,
const uint8_t* region_indices,
const uint8_t* height_enabled,
const double* object_gains,
double* output_interleaved);
EJOC_API ejoc_binaural_renderer_handle EJOC_CALL ejoc_binaural_renderer_create(void);
EJOC_API void EJOC_CALL ejoc_binaural_renderer_destroy(ejoc_binaural_renderer_handle handle);
EJOC_API int EJOC_CALL ejoc_binaural_renderer_reset(ejoc_binaural_renderer_handle handle);
EJOC_API const char* EJOC_CALL ejoc_binaural_renderer_last_error(
ejoc_binaural_renderer_handle handle);
EJOC_API int EJOC_CALL ejoc_binaural_renderer_configure_kernels(
ejoc_binaural_renderer_handle handle,
const double* qmf_analysis,
const double* hybrid_low,
const int16_t* hybrid_indices,
const double* hybrid_values,
uint32_t hybrid_count,
const double* qmf_basis,
const double* qmf_taps);
EJOC_API int EJOC_CALL ejoc_binaural_renderer_configure_room(
ejoc_binaural_renderer_handle handle,
uint32_t bands,
uint32_t allpass_count,
const uint32_t* allpass_delays,
const double* allpass_gains,
const uint32_t* fdn_delays,
const double* fdn_matrix,
uint32_t output_tap_delay,
const double* feedback_complex,
const double* output_taps,
const double* output_complex,
uint32_t extra_count,
const uint32_t* extra_delays,
const double* extra_fields_complex,
const double* extra_matrices);
EJOC_API int EJOC_CALL ejoc_binaural_renderer_process(
ejoc_binaural_renderer_handle handle,
const double* input16_interleaved,
const double* gains_complex,
const double* room_sends,
double output_gain,
double* output_stereo_interleaved);
/*
Native SOFA binaural renderer.
The handle owns the complete runtime: 64-QMF/77-hybrid analysis and synthesis,
fifth-order ACN/N3D real spherical-harmonic direction-field evaluation,
per-object whole-QMF-slot delay histories, six first-order image-source early
reflections, the shared unitary-FDN late room, the 120-180 Hz LFE low-pass and
the 961-sample latency compensation. The caller configures the filterbank
tables, the compiled HRTF field and the room constants once, then per 512-sample
block updates every source with ejoc_sofa_binaural_set_source() and calls
ejoc_sofa_binaural_process(). Process returns the number of trimmed stereo
samples written; the first 961 processed samples across calls are discarded.
*/
typedef void* ejoc_sofa_binaural_handle;
EJOC_API ejoc_sofa_binaural_handle EJOC_CALL ejoc_sofa_binaural_create(void);
EJOC_API void EJOC_CALL ejoc_sofa_binaural_destroy(ejoc_sofa_binaural_handle handle);
EJOC_API int EJOC_CALL ejoc_sofa_binaural_reset(ejoc_sofa_binaural_handle handle);
EJOC_API const char* EJOC_CALL ejoc_sofa_binaural_last_error(
ejoc_sofa_binaural_handle handle);
EJOC_API int EJOC_CALL ejoc_sofa_binaural_configure_kernels(
ejoc_sofa_binaural_handle handle,
const double* qmf_analysis,
const double* hybrid_low,
const int16_t* hybrid_indices,
const double* hybrid_values,
uint32_t hybrid_count,
const double* qmf_basis,
const double* qmf_taps);
EJOC_API int EJOC_CALL ejoc_sofa_binaural_configure_field(
ejoc_sofa_binaural_handle handle,
const double* coefficients,
const double* delay_coefficients,
const double* delay_bounds,
const double* band_centers,
double measurement_radius_m);
EJOC_API int EJOC_CALL ejoc_sofa_binaural_configure_room(
ejoc_sofa_binaural_handle handle,
const double* room_dims,
const double* listener_pos,
const double* wall_gains,
double speed_of_sound,
const uint32_t* fdn_delays,
const double* fdn_feedback,
double damping,
double fdn_output_gain,
const uint32_t* allpass_delays,
const double* allpass_gains,
uint32_t enable_early_reflections,
uint32_t enable_late_room);
EJOC_API int EJOC_CALL ejoc_sofa_binaural_set_source(
ejoc_sofa_binaural_handle handle,
uint32_t source,
const double* position_adm,
uint32_t profile,
double gain,
uint32_t enabled,
uint32_t special_lfe,
uint32_t fade);
EJOC_API int EJOC_CALL ejoc_sofa_binaural_process(
ejoc_sofa_binaural_handle handle,
const double* input16_interleaved,
uint32_t sample_count,
double output_gain,
double* output_stereo_interleaved);
EJOC_API int EJOC_CALL ejoc_sofa_binaural_finish(
ejoc_sofa_binaural_handle handle,
uint32_t flush_samples,
double* output_stereo_interleaved,
uint32_t capacity);
#ifdef __cplusplus
}
#endif
+486
View File
@@ -0,0 +1,486 @@
/*
* joc_core.h -- JustOneCacophony C++ Core, public ABI. Pure C.
*
* This header is the single authoritative definition of the Core's public
* parameter/error surface. Frontends (thin Python CLI, joc_dump, foobar2000,
* MPV, FFmpeg) only ever fill these POD structs and read these POD results;
* no audio data, no internal DSP concept, and no Python type crosses this
* boundary.
*
* Stability tiers
* ---------------
* [T1] host tier
* joc_abi_version / joc_version_string / joc_build_info, joc_error,
* joc_error_name / joc_error_stage, joc_last_error_detail.
* Stable, versioned, safe for media hosts.
*
* [T2] bitstream / verification tier
* joc_parse_eac3_frame, joc_parse_id14, joc_frame_params, joc_emdf_info.
* These deliberately expose the dequantized JOC matrix coefficients so the
* bitstream front-end can be validated bit-exactly and driven by tooling
* (joc_dump) and A/B harnesses. Media hosts must NOT use this tier; they
* use the task/stream API (added in later milestones).
*
* Conventions
* -----------
* * every struct's first two fields are struct_size / struct_version;
* * all arrays are fixed size and POD, no pointers, no allocation;
* * every function returns joc_error (JOC_OK == 0);
* * joc_last_error_detail() returns a thread-local message that stays valid
* until the next Core call on the same thread.
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#define JOC_ABI_VERSION 3u
#define JOC_FRAME_PARAMS_VERSION 1u
#define JOC_EMDF_INFO_VERSION 1u
#define JOC_TASK_CONFIG_VERSION 3u
#define JOC_TASK_RESULT_VERSION 2u
#define JOC_EVENT_VERSION 1u
#if defined(_WIN32)
#if defined(JOC_BUILD_DLL)
#define JOC_API __declspec(dllexport)
#else
#define JOC_API __declspec(dllimport)
#endif
#define JOC_CALL __cdecl
#else
#define JOC_API __attribute__((visibility("default")))
#define JOC_CALL
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* ------------------------------------------------------------------ */
/* Fixed layout constants (single source of truth for every frontend) */
/* ------------------------------------------------------------------ */
enum {
JOC_FRAME_SAMPLES = 1536, /* E-AC-3 frame = 24 * 64 */
JOC_TIMESLOTS = 24,
JOC_SUBBANDS = 64,
JOC_CORE_CHANNELS = 5, /* L R C Ls Rs (JOC order) */
JOC_MAX_CORE_CHANNELS = 7, /* dmx_config_idx 1/2/4 declare 7 */
JOC_OUTPUT_CHANNELS = 16, /* ch0 = LFE, ch1..15 = objects */
JOC_MAX_OBJECTS = 15,
JOC_MAX_DPOINTS = 2,
JOC_MAX_PARAMETER_BANDS = 23,
JOC_MAX_EMDF_PAYLOADS = 16,
JOC_SPEAKER_BLOCK_SAMPLES = 32,
JOC_BINAURAL_BLOCK_SAMPLES = 512,
JOC_BINAURAL_QMF_BANDS = 64,
JOC_BINAURAL_HYBRID_BANDS = 77,
JOC_QMF_HOP_SAMPLES = 64,
JOC_BINAURAL_LATENCY_SAMPLES = 961,
JOC_LFE_DELAY_SAMPLES = 1217
};
/* ------------------------------------------------------------------ */
/* [T1] library / error surface */
/* ------------------------------------------------------------------ */
typedef enum joc_error {
JOC_OK = 0,
JOC_ERR_INVALID_ARGUMENT,
JOC_ERR_INVALID_CONFIG,
JOC_ERR_OUT_OF_MEMORY,
JOC_ERR_IO,
JOC_ERR_UNSUPPORTED_PLATFORM,
JOC_ERR_LIBRARY_MISSING,
/* input / bitstream */
JOC_ERR_INPUT_NOT_FOUND,
JOC_ERR_INPUT_FORMAT,
JOC_ERR_EAC3_SYNCFRAME,
JOC_ERR_EMDF_TRANSPORT,
JOC_ERR_EMDF_SYNTAX,
JOC_ERR_JOC_SYNTAX,
JOC_ERR_JOC_UNSUPPORTED_VARIANT,
JOC_ERR_OAMD_SYNTAX,
JOC_ERR_OAMD_UNSUPPORTED_VARIANT,
JOC_ERR_BITSTREAM_TRUNCATED,
JOC_ERR_BITSTREAM_PADDING,
/* resources */
JOC_ERR_HRTF_NOT_FOUND,
JOC_ERR_HRTF_FORMAT,
JOC_ERR_HRTF_VERSION,
JOC_ERR_HRTF_HASH,
JOC_ERR_HRTF_UNSUPPORTED_CONVENTION,
/* rendering / output */
JOC_ERR_LAYOUT_UNSUPPORTED,
JOC_ERR_RENDER_FAILED,
JOC_ERR_OUTPUT_OPEN,
JOC_ERR_OUTPUT_WRITE,
JOC_ERR_OUTPUT_CLIP_ABORT,
JOC_ERR_ADM_VALIDATION,
/* task / stream */
JOC_ERR_CANCELLED,
JOC_ERR_STATE,
JOC_ERR_NOT_SUPPORTED,
JOC_ERR_INTERNAL
} joc_error;
JOC_API uint32_t JOC_CALL joc_abi_version(void);
JOC_API const char* JOC_CALL joc_version_string(void);
JOC_API const char* JOC_CALL joc_build_info(void);
/* Struct sizes, so a binding can assert its layout matches the library instead of
* assuming (mismatches are otherwise silent memory corruption). */
JOC_API uint32_t JOC_CALL joc_event_size(void);
JOC_API uint32_t JOC_CALL joc_task_config_size(void);
JOC_API uint32_t JOC_CALL joc_task_result_size(void);
JOC_API const char* JOC_CALL joc_error_name(joc_error code);
JOC_API const char* JOC_CALL joc_error_stage(joc_error code);
/* Thread-local structured detail for the most recent failing call. */
JOC_API const char* JOC_CALL joc_last_error_detail(void);
/* ------------------------------------------------------------------ */
/* [T2] bitstream / verification tier */
/* ------------------------------------------------------------------ */
/* One EMDF payload directory entry. bit_offset is the MSB-first bit
* position of the payload's first byte inside the syncframe, exactly as the
* EMDF container syntax defines it (payloads are not byte aligned in general). */
typedef struct joc_emdf_payload_info {
uint8_t id;
uint8_t reserved[3];
uint16_t sample_offset; /* EMDF outer smpoffst, 11 bits */
uint16_t reserved2;
uint32_t bit_offset;
uint32_t size; /* payload bytes */
} joc_emdf_payload_info;
typedef struct joc_emdf_info {
uint32_t struct_size;
uint32_t struct_version;
uint32_t start_bit; /* container syncword bit position */
uint32_t container_bytes; /* 4 + declared length */
uint32_t payload_count;
uint32_t reserved;
joc_emdf_payload_info payloads[JOC_MAX_EMDF_PAYLOADS];
} joc_emdf_info;
/* Per-object ID14/JOC descriptor plus its dequantized matrix.
* dq[dp][ch][pb] is zero filled outside [0,n_dpoints) x [0,n_channels) x
* [0,n_bands). Absent objects are entirely zero. */
typedef struct joc_object_params {
uint8_t present;
uint8_t num_bands_idx;
uint8_t n_bands;
uint8_t sparse; /* 0 = dense (MTX), 1 = sparse (IDX+VEC) */
uint8_t quant_idx; /* 0 = 96 levels, 1 = 192 levels */
uint8_t slope_idx; /* 0 = interpolate, 1 = step at offset_ts */
uint8_t num_dpoints_bits;
uint8_t n_dpoints;
uint8_t offset_ts[JOC_MAX_DPOINTS];
uint8_t reserved[2];
double dq[JOC_MAX_DPOINTS][JOC_MAX_CORE_CHANNELS][JOC_MAX_PARAMETER_BANDS];
} joc_object_params;
typedef struct joc_frame_params {
uint32_t struct_size;
uint32_t struct_version;
uint8_t dmx_config_idx;
uint8_t num_objects_bits;
uint8_t ext_config_idx;
uint8_t n_objects;
uint8_t n_channels; /* 5 or 7 */
uint8_t clipgain_x_bits;
uint8_t clipgain_y_bits;
uint8_t reserved;
uint32_t seq_count; /* 10-bit JOC sequence counter, parsed only */
uint32_t present_mask; /* bit i == object i present */
uint32_t data_end_bits; /* bit position just after joc_data */
uint32_t trailing_bits; /* bits left after joc_data (padding/ext) */
uint8_t tail_bytes[8]; /* first up to 8 trailing bytes, for A/B */
double clipgain; /* 1 + (y/32) * 2^(x-4), bit-exact */
joc_object_params objects[JOC_MAX_OBJECTS];
} joc_frame_params;
/* Parse an EMDF ID14 (JOC) payload. */
JOC_API joc_error JOC_CALL joc_parse_id14(const uint8_t* payload, size_t payload_size,
joc_frame_params* out_params);
/* Locate the contiguous JOC EMDF container inside one E-AC-3 syncframe and
* parse its ID14 payload. out_emdf may be NULL. */
JOC_API joc_error JOC_CALL joc_parse_eac3_frame(const uint8_t* frame, size_t frame_size,
joc_frame_params* out_params,
joc_emdf_info* out_emdf);
/* Copy one payload's bytes out of a syncframe (MSB-first bit extraction, which
* equals a memcpy for byte-aligned containers). out_size receives the payload
* byte count; pass out == NULL to query only the size. */
JOC_API joc_error JOC_CALL joc_extract_payload(const uint8_t* frame, size_t frame_size,
const joc_emdf_payload_info* payload,
uint8_t* out, size_t out_capacity,
size_t* out_size);
/* E-AC-3 syncframe traversal: given the offset of a frame start, report its
* byte length so a host can walk a bare E-AC-3 stream without duplicating
* frmsiz logic. Rejects resynchronisation (no silent recovery). */
JOC_API joc_error JOC_CALL joc_eac3_frame_bytes(const uint8_t* data, size_t size,
size_t offset, size_t* out_frame_bytes);
/* Strict trailing-bit check for the JOC payload (A/B robustness corpus).
* Returns JOC_ERR_BITSTREAM_PADDING when more than 7 bits are left over or the
* leftover bits are not zero. */
JOC_API joc_error JOC_CALL joc_check_id14_padding(const uint8_t* payload, size_t payload_size,
uint32_t* out_trailing_bits);
/* ================================================================== */
/* [T1] host tier: task, telemetry and control */
/* ================================================================== */
/* ---- 1. events ---------------------------------------------------- */
/*
* Events carry state only: frame/sample counters, stage, progress, statistics,
* warnings, errors and paths. Audio never travels through an event; a fixed
* size POD with no pointers keeps that enforceable (see the static assertion in
* the implementation).
*/
typedef enum joc_event_type {
JOC_EV_TASK_STARTED = 0x0001,
JOC_EV_TASK_STATE_CHANGED = 0x0002,
JOC_EV_TASK_COMPLETED = 0x0003,
JOC_EV_TASK_FAILED = 0x0004,
JOC_EV_TASK_CANCELLED = 0x0005,
JOC_EV_INPUT_OPENED = 0x0101,
JOC_EV_METADATA_INDEXED = 0x0103,
JOC_EV_HRTF_LOADED = 0x0110,
JOC_EV_RENDERER_INITIALIZED = 0x0120,
JOC_EV_PROGRESS = 0x0201,
JOC_EV_STAGE_CHANGED = 0x0202,
JOC_EV_JOC_FRAME_STATS = 0x0301,
JOC_EV_OAMD_STATS = 0x0302,
JOC_EV_OUTPUT_STATS = 0x0303,
JOC_EV_OUTPUT_OPENED = 0x0401,
JOC_EV_OUTPUT_FORMAT_DECIDED = 0x0402,
JOC_EV_OUTPUT_FINALIZED = 0x0403,
JOC_EV_LOG = 0x0501,
JOC_EV_WARNING = 0x0502,
JOC_EV_ERROR = 0x0503
} joc_event_type;
typedef enum joc_stage {
JOC_STAGE_IDLE = 0,
JOC_STAGE_INPUT = 1,
JOC_STAGE_METADATA = 2,
JOC_STAGE_DECODE = 3,
JOC_STAGE_JOC = 4,
JOC_STAGE_RENDER = 5,
JOC_STAGE_OUTPUT = 6,
JOC_STAGE_DONE = 7
} joc_stage;
enum { JOC_LOG_TRACE = 0, JOC_LOG_DEBUG = 1, JOC_LOG_INFO = 2, JOC_LOG_NOTICE = 3,
JOC_LOG_WARNING = 4, JOC_LOG_ERROR = 5, JOC_LOG_FATAL = 6 };
typedef struct joc_event {
uint32_t struct_size;
uint32_t type;
uint64_t sequence;
uint64_t timestamp_us;
uint64_t current_frame;
uint64_t total_frames;
uint64_t current_sample;
uint64_t total_samples;
uint32_t stage;
uint32_t backend;
double progress; /* 0..1, -1 = unknown */
double elapsed_seconds;
double realtime_factor;
uint64_t output_samples;
uint64_t output_bytes;
double output_duration_seconds;
joc_error error_code;
uint32_t log_level;
char stage_name[32];
char message[256];
} joc_event;
typedef void (JOC_CALL *joc_event_fn)(void* user, const joc_event* event);
typedef struct joc_event_sink {
uint32_t struct_size;
joc_event_fn callback;
void* user;
uint32_t min_type; /* 0 = no filter */
uint32_t max_type; /* 0 = no filter */
} joc_event_sink;
/* ---- 2. cancellation ---------------------------------------------- */
typedef struct joc_cancel_token joc_cancel_token;
JOC_API joc_cancel_token* JOC_CALL joc_cancel_token_create(void);
JOC_API void JOC_CALL joc_cancel_token_request(joc_cancel_token* token);
JOC_API int32_t JOC_CALL joc_cancel_token_is_requested(const joc_cancel_token* token);
JOC_API void JOC_CALL joc_cancel_token_destroy(joc_cancel_token* token);
/* ---- 3. task configuration and results ---------------------------- */
typedef enum joc_operation {
JOC_OP_ADM_BWF = 0,
JOC_OP_SPEAKER = 1,
JOC_OP_BINAURAL = 2
} joc_operation;
typedef enum joc_output_format {
JOC_FORMAT_FLOAT32 = 0,
JOC_FORMAT_PCM24 = 1
} joc_output_format;
typedef enum joc_binaural_mode {
JOC_BINAURAL_OFF = 0,
JOC_BINAURAL_NEAR = 1,
JOC_BINAURAL_FAR = 2,
JOC_BINAURAL_MID = 3
} joc_binaural_mode;
typedef enum joc_trajectory_mode {
JOC_TRAJECTORY_COMPACT = 0,
JOC_TRAJECTORY_DENSE64 = 1
} joc_trajectory_mode;
/* What to do when an int24 WAV would clip (peak outside [-1, 1]). Mirrors the
* reference CLI's --clip-action; ADM BWF output is always int24 and does not
* consult this because no alternative format exists there. */
typedef enum joc_clip_action {
JOC_CLIP_ASK = 0, /* prompt on stdin; an error when stdin is not a terminal */
JOC_CLIP_CONTINUE = 1, /* write int24, truncating out-of-range values */
JOC_CLIP_FLOAT32 = 2, /* switch the output to float32 */
JOC_CLIP_ABORT = 3 /* fail the task */
} joc_clip_action;
/* Compiled-HRTF cache policy for a SOFA input; the .jochrtf itself is an
* internal artifact of the compile step. */
typedef enum joc_hrtf_cache_policy {
JOC_HRTF_CACHE_NONE = 0, /* compile and discard */
JOC_HRTF_CACHE_MEMORY = 1, /* compile and keep in this process (default) */
JOC_HRTF_CACHE_DISK = 2 /* compile, reuse and write <cache_dir>/*.jochrtf */
} joc_hrtf_cache_policy;
enum { JOC_TASK_F_SKIP_SHA256 = 1u, JOC_TASK_F_KEEP_INTERMEDIATE = 2u,
JOC_TASK_F_QUIET = 4u, JOC_TASK_F_METADATA_ONLY = 8u };
typedef struct joc_task_config {
uint32_t struct_size;
uint32_t struct_version;
/* input */
const char* input_path; /* .eac3/.ec3/.m4a/... */
const char* ffmpeg_path; /* NULL = "ffmpeg" from PATH */
const char* bed_path; /* NULL = decode the core PCM with ffmpeg */
const char* work_dir; /* NULL = a temporary directory */
double eac3_drc_scale; /* 0 = DRC off (reference default) */
int32_t eac3_target_level; /* -31..0, 0 = not applied */
/* output */
uint32_t operation;
const char* output_path;
uint32_t output_format; /* requested format for speaker/binaural */
uint32_t flags;
uint32_t clip_action; /* joc_clip_action; JOC_CLIP_ASK by default */
/* rendering */
const char* speaker_layout_name;
uint32_t speaker_metadata_offset; /* default 1473 */
uint32_t binaural_mode;
const char* hrtf_path; /* .jochrtf */
const char* kernels_path; /* rosella_kernels.npz */
double binaural_tail_seconds; /* default 5.0 */
double binaural_tail_threshold; /* binaural only; 0 disables trimming */
uint32_t binaural_chunk_frames; /* accepted for CLI parity; no effect */
/* ADM */
uint32_t adm_binaural_mode; /* DBMD segment 10 encoding */
uint32_t trajectory_mode;
/* generic */
uint32_t object_delay_samples; /* default 1473 */
double gain_db; /* default 0 */
uint64_t duration_frames; /* 0 = the whole stream */
uint32_t progress_interval_frames; /* default 1000 */
uint32_t native_threads; /* 0 = automatic */
/* diagnostics */
uint32_t print_metadata; /* 0 none, 1 summary, 2 per frame */
const char* metadata_json_path; /* NULL = no JSON summary */
joc_cancel_token* cancel; /* optional */
/* Binaural HRTF input (config version 2): when hrtf_sofa_path is set the
* library compiles it with hrtf_cache_policy / hrtf_cache_dir / hrtf_radius_m
* and hrtf_path is unused. hrtf_path stays the advanced override that reads
* a .jochrtf directly. */
const char* hrtf_sofa_path; /* SOFA SimpleFreeFieldHRIR input */
const char* personalized_headphone_path; /* Rosella .personalized_headphone input */
const char* hrtf_cache_dir; /* disk policy directory */
uint32_t hrtf_cache_policy; /* joc_hrtf_cache_policy */
uint32_t reserved0;
double hrtf_radius_m; /* SOFA measurement-radius shell */
} joc_task_config;
typedef enum joc_task_status {
JOC_TASK_OK = 0,
JOC_TASK_FAILED = 1,
JOC_TASK_CANCELLED = 2
} joc_task_status;
typedef struct joc_task_result {
uint32_t struct_size;
uint32_t struct_version;
uint32_t status;
uint32_t error_code;
char error_message[512];
char error_stage[32];
uint64_t input_frames;
uint64_t output_samples;
double duration_sec;
uint32_t output_format_actual;
double output_peak;
uint64_t output_over_unity_values;
uint64_t output_file_bytes;
char output_sha256[65]; /* empty when skipped */
uint64_t oamd_payloads;
uint64_t oamd_transitions;
double t_decode_bed;
double t_render;
double t_write;
double t_total;
double t_render_dsp; /* speaker/binaural DSP calls only; 0 for ADM */
double t_write_file; /* disk writes including the finalize; >= t_write */
} joc_task_result;
typedef struct joc_validation_issue {
joc_error code;
uint32_t severity; /* 0 = info, 1 = warning, 2 = error */
char field[48];
char message[256];
} joc_validation_issue;
/* ---- 4. entry points ---------------------------------------------- */
/* Fills `issues` (up to `capacity`) and reports how many were produced.
* Returns JOC_OK when no *error*-severity issue was found. */
JOC_API joc_error JOC_CALL joc_task_validate(const joc_task_config* config,
joc_validation_issue* issues, uint32_t capacity,
uint32_t* count);
/* Runs the whole task on the calling thread. `sink` may be NULL; `out` may be
* NULL. On cancellation the partial output is removed and JOC_ERR_CANCELLED is
* returned with out->status = JOC_TASK_CANCELLED. */
JOC_API joc_error JOC_CALL joc_task_execute(const joc_task_config* config,
const joc_event_sink* sink, joc_task_result* out);
/* Serialises the stable subset of the result as JSON (no environment fields -
* those belong to the frontend). `needed` receives the required size including
* the terminator; a NULL buffer queries only the size. */
JOC_API joc_error JOC_CALL joc_task_result_to_json(const joc_task_result* result, char* buffer,
size_t capacity, size_t* needed);
/* The streaming (push/pull) tier lives in "joc_stream.h" so an embedder can
* include the narrow surface without the bitstream/verification tier. */
#ifdef __cplusplus
} /* extern "C" */
#endif
+120
View File
@@ -0,0 +1,120 @@
/*
* joc_stream.h -- streaming (push/pull) interface of the JustOneCacophony core.
*
* This is the narrow, embedder-facing surface: a player or decoder component
* includes only this header. It is the same shared library as joc_core.h, split
* so an integrator never has to see the bitstream/verification tier.
*
* A stream is a stateful instance for hosts that cannot wait for a whole file:
* the caller feeds E-AC-3 bytes and the core PCM of the same frames (or already
* rebuilt objects16) and pulls rendered PCM as soon as it is available. It
* mirrors the two library shapes a decoder library normally offers: this
* push/pull form for host-owned I/O, and joc_task_execute() in joc_core.h for
* library-owned file I/O.
*
* Contract:
* - all state is instance-private, so several streams coexist;
* - a stream is NOT thread safe: push and pull must come from one thread;
* - rendering is stateful (matrix interpolation, gain ramps, room tail), so a
* new position on the timeline requires decoding to continue from the start
* of the stream; there is no seek in this version;
* - the kernel latency is 961 samples for speaker/binaural output: the first
* pull after two 512-sample blocks, and flush() drains the binaural tail.
*/
#pragma once
#include "joc_core.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct joc_stream joc_stream;
typedef enum joc_stream_input {
JOC_STREAM_IN_EAC3 = 0, /* bare E-AC-3 syncframes (the metadata stream) */
JOC_STREAM_IN_PCM_OBJECTS16 = 1, /* 16-channel objects16, decoded by the host */
JOC_STREAM_IN_CORE_PCM = 3 /* the 5.1 core PCM of the pushed E-AC-3 frames */
} joc_stream_input;
typedef enum joc_stream_output {
JOC_STREAM_OUT_PCM_OBJECTS16 = 0, /* planar [16][samples] float32 */
JOC_STREAM_OUT_SPEAKER = 1, /* interleaved [samples][channels] f32 */
JOC_STREAM_OUT_BINAURAL = 2 /* interleaved [samples][2] f32 */
} joc_stream_output;
typedef struct joc_stream_config {
uint32_t struct_size;
uint32_t struct_version;
uint32_t input;
uint32_t output;
const char* speaker_layout_name;
uint32_t speaker_metadata_offset; /* default 1473 */
uint32_t binaural_mode; /* near|mid|far; 0 means mid */
const char* hrtf_path; /* binaural only */
const char* kernels_path; /* binaural only */
double binaural_tail_seconds; /* default 5.0 */
uint32_t object_delay_samples; /* default 1473 */
double gain_db; /* default 0 */
uint32_t native_threads;
uint32_t reserved;
} joc_stream_config;
typedef struct joc_stream_buffer {
uint32_t struct_size;
uint32_t struct_version;
uint32_t kind; /* which joc_stream_input/output this buffer carries */
uint32_t channels;
uint32_t sample_rate;
uint32_t sample_count; /* in: capacity / out: produced (per channel) */
uint32_t byte_count; /* in: capacity / out: consumed or produced bytes */
uint32_t reserved;
const uint8_t* bytes; /* EAC3 input */
const float* pcm; /* PCM input */
uint8_t* out_bytes; /* reserved for encoded outputs */
float* out_pcm; /* PCM output */
} joc_stream_buffer;
typedef struct joc_stream_status_info {
uint32_t struct_size;
uint32_t struct_version;
uint64_t frames_in;
uint64_t frames_out;
uint64_t samples_in;
uint64_t samples_out;
uint64_t bytes_in;
uint64_t buffered_samples; /* rendered but not yet pulled, per channel */
uint64_t oamd_payloads;
uint64_t oamd_transitions;
uint32_t output_channels;
uint32_t ended; /* 1 after flush() */
} joc_stream_status_info;
JOC_API joc_error JOC_CALL joc_stream_create(const joc_stream_config* config, joc_stream** out);
/* Feeds one buffer. Kind selects the path: EAC3 bytes, the core PCM of those
* frames, or objects16. Consumed counts are reported so a caller can resume
* from a partial push. */
JOC_API joc_error JOC_CALL joc_stream_push(joc_stream* stream, const joc_stream_buffer* input,
uint32_t* consumed_samples, uint32_t* consumed_bytes);
/* Copies as many rendered samples as fit into `output` (interleaved, or planar
* for PCM_OBJECTS16) and reports how many were produced; 0 means "push more". */
JOC_API joc_error JOC_CALL joc_stream_pull(joc_stream* stream, joc_stream_buffer* output,
uint32_t* produced_samples);
/* Marks the end of input and drains whatever the renderer still holds (the
* binaural room tail); pull the remaining samples afterwards. */
JOC_API joc_error JOC_CALL joc_stream_flush(joc_stream* stream);
/* Returns the instance to its initial state (kernel, ramps, timeline, room,
* parser, counters) so the same stream can be reused for another pass. */
JOC_API joc_error JOC_CALL joc_stream_reset(joc_stream* stream);
JOC_API joc_error JOC_CALL joc_stream_status(const joc_stream* stream,
joc_stream_status_info* out);
JOC_API joc_error JOC_CALL joc_stream_destroy(joc_stream* stream);
#ifdef __cplusplus
} /* extern "C" */
#endif