- LICENSE (GPL-3.0) at the root; an MIT LICENSE in each components/aes67_*
and in web/, so they travel with the code when reused.
- THIRD_PARTY.md: third-party code and licences, and the known conflict of
the Espressif binary audio libraries with GPL-3.0 (firmware contains cspot).
- CLAUDE.md: rule to keep GPL code out of the core; phase 2 item to replace
the Espressif binaries (opencore-aacdec, Speex resampler).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All sources (player, test signals) are 48 kHz; at 96 kHz Spotify and HLS
played at the wrong speed. Validation, the page and the docs allow 48000
only, and a 96000 stored before is reset to 48000 at boot (stored values
aren't re-validated, and the SDP would still have said 96 kHz).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core: aes67_tx_pink_noise(), a ready-made TX source: xorshift32 white noise
through Paul Kellet's pink filter, -18 dBFS RMS (peaks about -6 dBFS), same
on L and R. The player uses it like the tone (straight to TX, no volume or
gain). Checked on the board: -18 dBFS RMS, octave bands 63 Hz-4 kHz flat
within +-0.4 dB, L = R.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pull callback's contract said "stream channels", but the player always
delivers stereo, so a 1-channel stream went out as L,R,L,R at half speed.
Sources now always deliver AES67_TX_SRC_CHANNELS (2); TX maps to the stream:
2 ch as is, 1 ch = (L+R)/2 in 64 bit with mono_sum (can't clip), else L.
Checked: mono packets 156 B, 1000/s, 48 samples each, SDP L24/48000/1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The periodic TX timer had an arbitrary phase against packet boundaries,
so packets waited up to one packet time (different after every restart).
- One-shot esp_timer aimed at the next packet's due time from PTP, with
the clock re-read after sending.
- 1 kHz tone from a per-rate lookup table (one period = rate/1000
samples) instead of sinf() per sample.
Measured (RTP time - arrival, 4 ms vs 1 ms packets, expected -3.07 ms
incl. wire time): -3.3 ms extra before, now -3.14 ms. Tone within
1.0 LSB of the ideal PTP-phased sine, no gaps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- aes67_tx: TX task woken by an esp_timer every packet time; sends every
packet whose last sample is in the past (PTP time), RTP ts =
(sample index from PTP + clk_offset) mod 2^32, packets aligned to
multiples of the packet size. Sends only while PTP is locked; resyncs
on lock, clock step or > 20 ms lag. L24/L16 big-endian, TTL/DSCP/
multicast IF from config; a config save restarts TX.
- Built-in 1 kHz tone at -18 dBFS, phase from the PTP sample index;
aes67_tx_set_source() pull callback for later sources (underruns
counted, padded with silence). status: tx_packets, underruns.
- Verified with a receiver script on the PC: 1 ms L24 stereo 1000/s,
0 gaps, ts step 48, tone -18.00 dBFS within 1.2 LSB of the ideal
PTP-phased sine; also L16 mono 0.333 ms, 0.125 ms (8000/s) and 4 ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- aes67_web/cfg.c: cfg_register(group, defaults, validate, apply),
cfg_override_defaults() for project defaults, cfg_get(). Stored values
are loaded on register and merged over defaults (known keys, matching
types). NVS keeps only keys that differ from defaults, one JSON string
per group, so new firmware defaults still reach untouched settings.
- POST /api/config: type check against defaults, per-group validation,
nothing stored unless every group passes; 400 "group.key: reason".
Unknown keys/groups are ignored. Returns the new config.
- Core groups registered by their components with the UI's ranges:
ptp (aes67_ptp), aes67 (aes67_tx, incl. multicast range and mono_sum
=> 1 channel), net/inet (aes67_net), log (aes67_syslog).
- Project: PROJECT.def overrides (hostname p4-aes67, name P4 AES67) and
the "source" group in main/project_cfg.c.
- Config is stored only; nothing is applied live yet.
- Verified on board: defaults, valid save, 8 rejected values (nothing
stored), persistence across reboot, restore to defaults.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- aes67_web: httpd (wildcard URI matching), serves web/index.html embedded
via EMBED_TXTFILES, status registry (status_register) and route registry
(web_register_uri), GET /api/status, POST /api/reboot (restarts 0.5 s
after the response).
- Status fields come from each core component: aes67_net (ip, aes67_ip,
mac, link), aes67_health (fw, uptime_s, heap_free, psram_free),
aes67_tx stub (tx_packets, underruns = 0). No ptp object yet, so the UI
shows "no data".
- Verified on board: / serves index.html byte-identical (27601 B),
/api/status returns live values, reboot via API returns in ~12 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Board checked with esptool: ESP32-P4 rev v1.3 (engineering silicon),
32 MB GigaDevice flash. Rev <3 support and 32 MB set in sdkconfig.defaults.
- OTA partition layout: nvs, otadata, phy_init, ota_0/ota_1 (6 MB each,
below 16 MB), coredump; no factory app. Partition table at 0x10000 so
the bootloader can grow.
- App rollback enabled in the bootloader from the start.
- PROJECT_VER from git describe --tags --always --dirty.
- Empty stubs for the nine aes67_* core components; main logs version
and chip revision.
- Built with ESP-IDF v5.5.5; not yet flashed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>