Change: audio, input and frontend libraries¶
Date: 2026-10-21. Owner: audio/input/frontend engineer. Interface for integrators: docs/audio-input-frontend-interface.md.
Objective¶
Provide the host side of audio, input and the window for Napple as three static libraries with plain C headers,
independent of IPC marshaling and of any particular game (audio/, input/, frontend/). Nothing under runtime/
or gfx/ was modified.
Files¶
| Path | Purpose |
|---|---|
audio/include/napple_audio.h, audio/src/*.c |
audren core (audren.c update parser and voice state machine, mix.c mixer, reverb.c/effects.c DSP), output engine (engine.c), audout (audout.c), hwopus (hwopus.c), util.c |
audio/tests/ |
test_audren, test_effects, test_audout, test_hwopus, test_engine, test_threads, demo_sine, wire.h (blob builder) |
input/include/napple_input.h, input/src/ |
hid_shm.c (pure shared memory writers), input.c (mapping, thread, files), bindings.c (defaults, validation, JSON), names.c, motion.c, gamepad.m (GameController, CoreHaptics), tinyjson.h |
input/tests/ |
test_hid, test_bindings, test_motion, test_input, demo_pads.m |
frontend/include/napple_frontend.h, frontend/src/ |
frontend.m (window, view, menu, focus, screenshot, vsync), overlay.m, textbar.m, settings.c (pure: settings JSON, paths, text validation, UTF, frame limiter), tinyjson.h |
frontend/tests/ |
test_settings, test_window.m, demo_window.m |
docs/audio-input-frontend-interface.md |
interface and integration instructions |
Logic¶
- audio:
napple_audren_request_updatevalidates every section of the update blob against the header (sections may be unaligned: copied with memcpy), applies voice/mix/effect/splitter/sink parameters, and writes the output blob. A render thread (or the caller in manual mode) mixes one frame (240 samples) per tick under the renderer lock: voices decoded along the exact sample path the voice clock consumes, linear resampling, Q14/float biquads, volume, channel resource or splitter routing, submixes deepest first, effects in order, device or circular sink. Output goes to a per-source lock-free ring; one Core Audio unit sums sources at 48 kHz stereo with master volume and clipping. Latency: prefill 20 ms (default), device buffer requested 256 frames, backlog trimmed after stalls. audout is a 5 ms pacing thread feeding the same engine; hwopus wraps libopus (single and multistream). - Guest-caused protocol violations call
napple_audio_fatal, which longjmps to the public entry (request update) or to the render loop; the host never aborts because of guest data (the renderer is muted after a render-path error). - input:
napple_input_updatemerges keyboard, mouse and GameController state per npad slot, picks the style from the guest-supported set, and pushes states into the rings withhid_shm.c. Ring semantics: header {timestamp, 17, tail, count}, entries {outer sampling number, state}. Bindings:input.jsonper game, atomic write, invalid files are never overwritten. Motion: complementary filter (quaternion) -> direction matrix, angle, angular velocity in revolutions/s. Rumble: CoreHaptics engines per handle fed by a 10 ms timer. - frontend: NSWindow with a layer-backed view whose layer is a
CAMetalLayer; events forwarded to input; overlay and text bar are subviews (work in full screen); settings applied on the main thread, saved atomically, announced by callback.
Decisions¶
- Reuse of Ryujinx.Audio-derived DSP (reverb3d, delay, reverb, compressor, limiter) and wire formats through the MIT reference ports (see THIRD_PARTY_NOTICES.md), restructured; TOTK-specific diagnostics and env switches removed.
- Output engine is shared (one Core Audio unit), not one unit per renderer, so audren + audout + several renderers do not fight over the device.
tinyjson.his duplicated in input/ and frontend/ (independent libs); bothmake testcheck the copies are identical.- Layout offsets are data (
napple_hid_layout) because only part of the Npad entry could be verified offline. - The overlay is a subview rather than a child window: simpler focus handling, works in full screen.
Risks and assumptions (honest list)¶
- HID Npad entry offsets past the first lifo are reconstructed from public documentation and not validated against a
running title (
device_type, system properties, battery are written at derived offsets;device_typeandsystem_propertiesare written as zero). Verification plan: run LAS/SMO withNAPPLE_HID_DUMPand compare the offsets the game reads. - Frame-rate cap, internal resolution and vsync are only settings plus helpers: gfx and the runtime must apply them.
- DualSense/Pro controller paths (GameController mapping, motion, haptics) were written but could not be run: no controller was attached to the test Mac. Keyboard/mouse paths are tested through synthetic events.
- audren: performance metrics are not produced; voice drop never triggers; REV1..4 headers are accepted but untested.
- HwOpus: the library does not call
write_prepareon the PCM buffer; the runtime must (see interface doc). - Screenshot fallback uses
screencapture -l, which may need Screen Recording permission; gfx can provide a frame viaon_screenshot. - Movie (VP9) decoding from the reference (
movie_host.c) is out of scope of this change; libvpx is installed (brew install libvpx) for a follow-up. - THIRD_PARTY_NOTICES.md at the repo root must credit Ryujinx (MIT, audio DSP and layouts), MacNativeGames/TOTK runtime and LASRecomp (MIT) for the reused parts.
Tests executed¶
| Command | Result |
|---|---|
make -C audio test |
audren 3410 checks, effects 1253, audout 506, hwopus 22, engine 11, threads 2: all pass |
make -C audio asan / tsan |
clean (UBSan, ASan, TSan) after fixing unaligned section reads and unlocked getters |
NAPPLE_TEST_DEVICE=1 build/test_engine, make -C audio demo |
real Core Audio output opened (48 kHz, 256-frame buffer), 0 underruns / 0 overruns in the 1.5 s demo, measured latency about 24 ms |
make -C input test |
hid 182, bindings 22 (incl. 20k fuzz iterations), motion 16, input 22: all pass |
make -C frontend test |
settings 30 (fuzz, traversal, UTF, limiter), window 13 (real AppKit loop: Metal layer, settings persistence and reload, overlay callbacks, scripted swkbd, quit path): all pass |
NAPPLE_DEMO_OVERLAY=1 build/demo_window + screencapture -x |
window with colour-cycling CAMetalLayer at 180 fps and the overlay (Video tab) rendered correctly; Controls/Game tabs and the swkbd bar were not exercised interactively |
Recommended, not done: run with the real games; fuzz napple_audren_request_update with captured update blobs; test
Bluetooth/Pro Controller rumble; long-run (hours) audio underrun statistics.