Skip to content

atmos renderer#82

Open
tryptz wants to merge 12 commits into
mainfrom
claude/atmos-renderer-dsp-pipeline-li3sg0
Open

atmos renderer#82
tryptz wants to merge 12 commits into
mainfrom
claude/atmos-renderer-dsp-pipeline-li3sg0

Conversation

@tryptz

@tryptz tryptz commented Jul 20, 2026

Copy link
Copy Markdown
Owner

No description provided.

tryptz added 4 commits July 19, 2026 23:47
Land the first self-contained, verifiable layer of the E-AC-3 JOC object
renderer described in the renderer plan, clean-room from the public ETSI
specs and dependency-free so it builds under the NDK and unit-tests on a
host toolchain.

Native (cpp/atmos):
- bit_reader.h  MSB-first bit reader + ETSI variable_bits escape
- emdf.h        EMDF container framing walk; locates OAMD/JOC payloads
- oamd.h        OAMD position coordinate math + unit-cube -> render-space map
- vbap.h        VBAPBedPanner: direction -> 5.1/7.1/7.1.4 speaker gains
                (2-D adjacent-pair + 3-D tightest-triplet VBAP)
- atmos.cpp     TU so the NDK type-checks the headers for every shipped ABI
- tests/atmos_tests.cpp  489 host checks, all green (round-trips, coordinate
                boundaries, VBAP source-at-speaker/energy/non-negativity)
- monochrome_atmos static CMake target + README documenting scope

Domain infra (JVM-unit-tested):
- TrackSource, RendererMode (Direct/Object/Bed+HRTF), ChannelLayout
- isDolbyAtmos / isAtmosCapableCodec detection mirroring isThxSpatialAudio
- RendererProfile with safe passthrough-stereo defaults

Scope is honest: the QMF filterbank, JOC decoder, HRTF back-end and the JNI/
Media3 wiring need reference Atmos content to validate and are documented as
TODO in atmos/README.md, not stubbed to look finished.
Connect the Atmos domain infra to the persistence layer, mirroring the
existing THX Spatial Audio path:

- LocalTrackEntity gains an isDolbyAtmos column; DB v9->v10 migration adds
  it to local_tracks and backfills rows whose title/album names "Dolby
  Atmos" (local-only, since sideloaded Atmos files land in the scan). Real
  migration, registered alongside MIGRATION_8_9.
- TagReader detects Atmos at scan time from the MIME / metadata via
  isDolbyAtmos() and carries it on AudioTags; MediaScanner persists it.
  Extension-only .ec3/.eac3 stays 'capable' not 'Atmos' until the native
  demux confirms JOC.
- PreferencesManager persists a RendererProfile (mode / layout / HRTF id)
  as JSON, same shape as the player-glass blobs. Kept device-local (not in
  the cloud-sync allow-list) since layout tracks the DAC and HRTF is a
  local measurement.

The channel layout needs no new column: ChannelLayout.fromChannelCount
derives it from the existing channels field.
Settings > Audio > Spatial Audio > Atmos Renderer Configuration: a full
renderer config screen plus a top-down speaker map that lights the
channels the current mix drives.

Domain (RendererProfile expanded, JVM-tested):
- StereoDownmixMode (Binaural / Lo-Ro / Lt-Rt) and DrcMode (Off..Heavy)
- typical renderer fields: mode, auto-detect layout, target layout,
  stereo downmix, HRTF profile, binaural strength, height virtualization,
  bass management + crossover + LFE gain, DRC, dialogue normalization
- clamped() bounds continuous fields; effectiveLayout() resolves the
  auto-detect flag; ChannelLayout.speakers() is the shared speaker model
  (angles mirror cpp/atmos/vbap.h). Persisted device-local, clamped on I/O.

UI:
- AtmosRendererScreen + AtmosRendererViewModel (debounced persistence)
- SpeakerLayoutMap: Canvas top-down room, each active channel drawn on its
  azimuth with a soft pulsing radial bloom; height speakers on an inner
  ring, LFE below the listener
- chip selectors, labeled sliders, reset-to-defaults
- route registered; entry added under Audio > Spatial Audio

Tests cover the new defaults, clamping (incl. NaN), effectiveLayout, and
the speaker map (one speaker per channel, exactly one LFE, height only on
7.1.4).
Faithful C# -> C++ port of Cavern's 64-band complex QMF analysis/synthesis
filterbank (QuadratureMirrorFilterBank.cs/.Process.cs/.Amp.cs) plus the
QMath multiply/accumulate primitives it builds on. This is the QMF domain
the JOC upmix operates in (plan A5).

- quadrature_mirror_filterbank.h: 640-tap prototype filter reproduced
  verbatim (extracted programmatically, not retyped), exact cos/sin
  modulation and delay-line indexing
- qmath.h: the multiply-and-set/add + dot primitives
- host reconstruction test: analysis -> synthesis reconstructs a broadband
  multi-sine at correlation 1.00000 with the expected 577-sample delay
- included from atmos.cpp so the NDK type-checks it on every ABI

LICENSING: Cavern is NOT MIT. It ships under a custom non-commercial,
no-ads licence that requires attribution + a source link and the creator's
permission for public/commercial use, and applies virally to including
projects. cavern/NOTICE.md reproduces the licence verbatim and records the
obligations; per-file headers link the upstream source. The cavern/ subtree
is therefore NOT clean-room -- the rest of cpp/atmos still is. Obtaining the
creator's permission before any public/monetized release is an owner action,
flagged in NOTICE.md.
@supabase

supabase Bot commented Jul 20, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project lvzorvfhhopillzlwgau because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

tryptz added 8 commits July 20, 2026 01:51
Faithful C# -> C++ port of the Joint Object Coding decode path — the step
that reconstructs Atmos objects from the E-AC-3 core bed + JOC side data
(plan A6), building on the already-ported QMF filterbank.

- joc_tables.h: Huffman code tables (nodes extracted verbatim, not retyped),
  joc_num_bands, and the parameter-band mapping expanded once into a
  subband->parameter-band LUT (SetupStaticCache)
- joint_object_coding.h: frame decode (header/info/data + Huffman), coarse
  dequantization, and per-timeslot mixing-matrix interpolation (1/2 data
  points, steep/smooth). Reuses the existing MSB-first BitReader, which is
  bit-identical to Cavern's BitExtractor
- joint_object_coding_applier.h: QMF-domain channel->object mix
  (object = sum_ch channelQMF[ch] * mixMatrix[ch]) + inverse QMF + gain

Deviations, behaviour-preserving: Cavern's ThreadPool fan-out is sequential;
exceptions become a valid() flag (NDK is -fno-exceptions); the sparse WIP
path matches upstream (dequantize with gainStep 0).

Host tests (87 checks, all green): Huffman encode/decode round-trip over the
real tables, parameter-band mapping, the coarse dequant recurrence, and an
end-to-end decode -> mixing matrices -> applier run producing finite output.
Numeric bit-exactness still needs A/B vs reference Atmos content.

LICENSING unchanged: this is under Cavern's non-commercial/no-ads licence
(attribution + source link, creator's permission for public/commercial use).
cavern/NOTICE.md lists every ported file and the obligations.
Faithful C# -> C++ port of ObjectInfoBlock.cs + the OAMD enums — the
per-object Object Audio Metadata decode that gives each object its position
(plan A4), to pair with the JOC-reconstructed object PCM.

- object_info_block.h: absolute/differential position, gain (with the 3 dB
  anti-clip), size, and anchor (bed/room/screen) decode. Scales match the
  clean-room oamd.h (xy 1/62, z 1/15). NonStandardBedChannel / ObjectAnchor
  enums included. read_signed reproduces Cavern's BitExtractor.ReadSigned
  faithfully (it collapses to 0 — kept for A/B parity, with the C# shift-mask
  applied to avoid C++ shift UB).
- qmath.h: add db_to_gain (QMath.DbToGain).
- bitstream decode is reproduced exactly (bit alignment preserved); Cavern's
  UpdateSource() render integration (Listener/Source) is NOT ported —
  resolved_position() applies the differential + render-space map instead.

Host-tested (16 checks): absolute position -> render-space centre, gain,
bed->speaker anchor, and the degenerate ReadSigned. Compiles under the NDK
production flags; wired into atmos.cpp for per-ABI coverage.

Still to port: the OAMD framing (ObjectAudioElementMetadata, program
assignment) and the EMDF container that drive these blocks. Licensing
unchanged — see cavern/NOTICE.md.
Completes the Atmos side-data decode path (plan A3): the framing that ties
OAMD object positions and JOC together.

- object_metadata.h: OAElementMD (element read, update timing, per-block
  ramps) + ObjectAudioMetadata (program/bed assignment, object count). The
  EMDF VariableBits helpers and ReadBits (reversed fill) are reproduced
  verbatim.
- extensible_metadata_decoder.h: ExtensibleMetadataDecoder — scans the EMDF
  syncword (0x5838) and dispatches OAMD (id 11) / JOC (id 14) to their
  decoders, holding both.
- bit_reader.h: add set_position/back_position (the EMDF walk seeks past
  payloads by absolute bit position) — clean-room generic accessors.

Scope: bitstream decode reproduced exactly (bit alignment preserved);
Cavern's UpdateSources() render integration (Listener/Source) is not ported,
decoded state is exposed via accessors for a future renderer.

With this the full EMDF -> OAMD + JOC -> QMF upmix chain is ported. Host
tests (49 checks): EMDF VariableBits round-trip, ReadBits, and program
assignment (dynamic-only + LFE, bed LFE-only). Compiles under NDK production
flags; wired into atmos.cpp. Licensing unchanged (cavern/NOTICE.md).

Still to build: E-AC-3 core audio decode (bed PCM), container demux, HRTF
back-end, and the JNI/Media3 wiring.
Expose the ported EMDF -> OAMD + JOC decode chain to the app, off the
player's critical path.

- atmos_jni.cpp: JNI bridge — nativeSelfCheck() and
  nativeDecodeEmdfObjectCount() (walk an EMDF buffer, return JOC object
  count, exercising the full decode path natively). Host-compiles clean
  against a real jni.h.
- monochrome_atmos CMake target: STATIC -> SHARED so
  System.loadLibrary("monochrome_atmos") works; still type-checked for
  every ABI on each CI run.
- AtmosEngine.kt: lazy, failure-tolerant loader (ensureLoaded returns false
  instead of throwing if the .so is missing) + the external-fun surface.

Nothing loads the library at runtime yet, so playback is unaffected. This is
the boundary the render/output wiring grows into once a core E-AC-3 bed
decoder is chosen (Cavern port vs FFmpeg eac3, plan Option 1).

Rationale: the E-AC-3 core audio decode is a large, multi-file, framework-
entangled Cavern codec that can't be host-validated without reference AC-3
content; this JNI step is bounded, CI-verifiable, and no-regret under either
core-decoder choice.
… logos

Reworks the Streaming onboarding step to present the catalog sources (TIDAL, Qobuz, Apple Music) plus the Spotify radio connector, each with its brand logo. Apple Music is flagged Dolby Atmos capable and, like the others, routes through the self-hosted TrypT HiFi instance. Adds vector drawables logo_tidal/logo_qobuz/logo_spotify/logo_apple_music; ServiceCard renders a drawable (tinted for the monochrome TIDAL mark, brand colour otherwise).
…stance

Mirrors the Qobuz catalog wiring end to end: SourceType.APPLE, SourceMode.APPLE_ONLY, an Apple Music instance URL (falls back to the Qobuz URL since it's the same TrypT HiFi server), InstanceManager.appleInstanceOrNull, HiFiApiClient.searchApple hitting /api/apple/get-music (reuses the Qobuz-shaped envelope + mappers), MusicRepository.searchApple, Track.toAppleUnifiedTrack (SourceType.APPLE, reuses QobuzCached playback), a third parallel fan-out in SearchViewModel (runs under BOTH or APPLE_ONLY), a SearchSourceFilter.APPLE chip, and Settings: an 'Apple only' catalog toggle + an Apple Music URL field. NOTE: not compile-tested (no local Android SDK); build to verify. Apple pagination is first-page only for now; playback reuses the Qobuz cached path (both Qobuz and Apple can stream).
…+ cloud cache)

Adds PlaybackSource.AppleCached and routes Apple tracks off the Qobuz path onto the instance's /api/apple/download-music, which returns the wrapper-resolved manifest. StreamResolver.resolveAppleCached reads manifest.delivery.streamUrl (the cloud-cached decrypted ALAC/Atmos file, Range-capable) and streams it directly. Atmos-flagged tracks request the atmos variant so decryption flows through the Tailscale wrapper. Adds AppleDownloadEnvelope model, HiFiApiClient.getAppleStreamUrl, MusicRepository.appleStreamUrl; repoints toAppleUnifiedTrack to AppleCached. NOT compile-tested (no local Android SDK). Offline Download-to-device (DownloadWorker) still uses the Qobuz path and needs a source flag - follow-up.
DownloadWorker now routes Apple tracks to the wrapper-backed Apple download endpoint instead of the Qobuz one, saving the cloud-cached decrypted file as .m4a (audio/mp4), skipping FLAC header-sniffing/tagging. Apple track ids are tracked in QobuzIdRegistry (registerAppleTrack/isAppleTrack, persisted), populated by searchApple; the worker detects Apple via the registry so no call-site changes are needed. NOT compile-tested.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant