Index Mimir perfect machine domain
This commit is contained in:
parent
f52af868b5
commit
cb36734aa8
13 changed files with 1222 additions and 0 deletions
|
|
@ -48,6 +48,8 @@ Start with:
|
|||
camera/sensor evidence.
|
||||
- [docs/code-algorithm-map.md](docs/code-algorithm-map.md) for the current
|
||||
source-level code map.
|
||||
- [docs/perfect-machine-domain-index.md](docs/perfect-machine-domain-index.md)
|
||||
for the indexed problem-domain map and implementation cuts.
|
||||
|
||||
Naming note: **Fensalir** is the engine/windowing/rendering/D3D12 layer. Older
|
||||
research notes may still use the previous engine name.
|
||||
|
|
|
|||
|
|
@ -1418,6 +1418,10 @@ Linked notes:
|
|||
- `research/feedback-calibration/summary.md`: room/speaker/mic calibration.
|
||||
- `research/perfect-machine-hypotheses/summary.md`: hypotheses added by this
|
||||
documentation pass.
|
||||
- `docs/perfect-machine-domain-index.md`: indexed domain map for future
|
||||
implementation passes.
|
||||
- `research/perfect-machine-study-2026-05-23/`: architecture rumination,
|
||||
optimization ledger, references, and sample implementation sketches.
|
||||
|
||||
Hypotheses:
|
||||
|
||||
|
|
|
|||
239
docs/perfect-machine-domain-index.md
Normal file
239
docs/perfect-machine-domain-index.md
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
# Mimir Perfect Machine Domain Index
|
||||
|
||||
## What This File Is
|
||||
|
||||
This is the domain index for future implementation passes. It organizes the
|
||||
whole Mimir problem space by authority, evidence type, algorithm, hot path, and
|
||||
open architectural cut.
|
||||
|
||||
Read this with:
|
||||
|
||||
- `docs/code-algorithm-map.md` for source-level ownership.
|
||||
- `docs/audio-field.md` for current audio sync narrative.
|
||||
- `docs/native-capture-cadence.md` for measured camera/USB evidence.
|
||||
- `research/perfect-machine-study-2026-05-23/architecture-rumination.md` for
|
||||
the long-form design argument.
|
||||
- `research/perfect-machine-study-2026-05-23/index.md` for the study directory
|
||||
and sample-code index.
|
||||
- `research/perfect-machine-study-2026-05-23/optimization-ledger.md` for hot
|
||||
loop candidates.
|
||||
- `research/perfect-machine-study-2026-05-23/samples/README.md` for sample
|
||||
implementation sketches.
|
||||
|
||||
## One-Sentence Machine
|
||||
|
||||
Mimir is a bounded-latency realtime field computer: it ingests many clocks,
|
||||
places their evidence inside one rolling temporal window, extracts audio/video
|
||||
geometry from that window, and emits an OBS-usable program surface without
|
||||
letting OBS become the synchronization authority.
|
||||
|
||||
## Authority Index
|
||||
|
||||
| Authority | Owns | Must Not Own |
|
||||
| --- | --- | --- |
|
||||
| `Mimir.Runtime` | stream identity, buffers, sync reports, calibration model loading, source polling, telemetry state | hot DSP actuator, dense visual fusion, broadcast composition |
|
||||
| Native capture | direct driver callbacks, device timestamps, payload handles, low-jitter capture loops | runtime policy, UI state, OBS composition |
|
||||
| `native/reservoir` | shared-edge retention and typed views | payload memory interpretation, rendering, DSP |
|
||||
| Fensalir | window, UI, D3D12 bridge, GPU fusion, program video publication | clock authority, audio actuator |
|
||||
| Faust/native DSP | fractional delay, resampling, separation, spatialization, stems | stream discovery, UI |
|
||||
| OBS | final broadcast composition | synchronization, capture timing |
|
||||
| Raven/phones | local decode of codebook/schedule observations | canonical clock authority |
|
||||
|
||||
## Evidence Taxonomy
|
||||
|
||||
### Audio Timing Evidence
|
||||
|
||||
- ASIO loopback: strongest local reference; same interface clock as Scarlett mic
|
||||
channels on Starfire.
|
||||
- Physical mic chirp-bin decode: acoustic timing plus response surface; current
|
||||
failure mode is sparse symbols and room/path coloration.
|
||||
- Passive program audio: relative delay/drift hint from real content; useful
|
||||
when confidence is earned, not a canonical code timeline.
|
||||
- Network audio: delayed observations from Raven/phones; must carry decoded
|
||||
local timing quality, not become a remote clock.
|
||||
|
||||
### Audio Calibration Evidence
|
||||
|
||||
- Magnitude response: which emitted bins survive per output/mic path.
|
||||
- Confusion matrix: expected symbol versus observed bin energy/symbol.
|
||||
- Phase coherence: whether a path has stable phase enough for group-delay
|
||||
correction.
|
||||
- Delay hypotheses: global delay/bin-shift candidates for whole-window decode.
|
||||
- SRO: delay slope over time, feeding resampler control.
|
||||
|
||||
### Visual Evidence
|
||||
|
||||
- Leap stereo IR: timing/depth candidate, topology-sensitive but high-value.
|
||||
- PS3 Eyes: high-rate tracking witnesses.
|
||||
- Kiyo/Kiyo Pro: RGB context and texture, with Kiyo Pro currently cadence/USB
|
||||
limited.
|
||||
- Future Fensalir GPU state: features, rays, surface/material claims, splats,
|
||||
confidence, and render packets.
|
||||
|
||||
### Network Evidence
|
||||
|
||||
- SRT/FFmpeg bridge: useful v1 transport witness, not synchronized program
|
||||
authority.
|
||||
- Future Raven/phone agents: source-local decode/calibration reporters.
|
||||
|
||||
## Algorithm Families
|
||||
|
||||
### Controlled Chirp-Bin Active Decode
|
||||
|
||||
Purpose:
|
||||
|
||||
- Place each mic stream on canonical time.
|
||||
- Learn response/confusion/phase/group-delay.
|
||||
|
||||
Current live shape:
|
||||
|
||||
1. render deterministic fixed-slope chirp-bin codebook;
|
||||
2. detect candidate event windows via cheap energy trace;
|
||||
3. dechirp each candidate;
|
||||
4. score a small bin bank;
|
||||
5. preserve ambiguous symbol/time candidates;
|
||||
6. select code-valid de Bruijn triplets;
|
||||
7. fit a coherent source clock;
|
||||
8. refine source offset against scheduled waveform;
|
||||
9. emit calibration model evidence.
|
||||
|
||||
Pressure:
|
||||
|
||||
- The hot loop must move from scalar per-window scoring toward SIMD/GPU batched
|
||||
dechirp/bin evaluation once stream counts rise.
|
||||
|
||||
### Passive GCC-PHAT Program-Audio Sync
|
||||
|
||||
Purpose:
|
||||
|
||||
- Use existing music/game audio when it is informative.
|
||||
|
||||
Current live shape:
|
||||
|
||||
1. window reference/candidate audio;
|
||||
2. mean remove, preemphasize, Hann window;
|
||||
3. FFT both;
|
||||
4. phase-normalized cross-spectrum;
|
||||
5. inverse FFT to lag correlation;
|
||||
6. peak/refinement/confidence.
|
||||
|
||||
Pressure:
|
||||
|
||||
- Passive confidence should be multi-window and stateful before it can suppress
|
||||
active watermark emission for long periods.
|
||||
|
||||
### Fractional Delay And SRO Actuation
|
||||
|
||||
Purpose:
|
||||
|
||||
- Turn measurement into aligned samples.
|
||||
|
||||
Likely shape:
|
||||
|
||||
- Per-source delay state drives a fractional delay line.
|
||||
- SRO state drives an asynchronous sample-rate converter.
|
||||
- Fast path belongs in Faust/native DSP, not C#.
|
||||
- Control loop consumes `MimirAudioSynchronizationState`.
|
||||
|
||||
Candidate filters:
|
||||
|
||||
- Farrow/Lagrange fractional delay for tiny offset corrections.
|
||||
- Polyphase windowed-sinc resampler for higher quality.
|
||||
- PLL-like state on delay slope to keep drift stable.
|
||||
|
||||
### Visual Fusion
|
||||
|
||||
Purpose:
|
||||
|
||||
- Turn synchronized frames into a usable volumetric scene.
|
||||
|
||||
Likely shape:
|
||||
|
||||
- Native capture pushes typed frame handles and timestamps.
|
||||
- Fensalir extracts features/depth/optical flow on GPU.
|
||||
- Native reservoir preserves rolling evidence.
|
||||
- Fusion emits surface/material/render claims.
|
||||
- Program video publishes via Spout2.
|
||||
|
||||
Pressure:
|
||||
|
||||
- Do not build a CPU image pipeline because it is easy to inspect. Six cameras
|
||||
make that path collapse into a pile of excuses.
|
||||
|
||||
## Hot Loop Budget
|
||||
|
||||
### Audio
|
||||
|
||||
Given 192 kHz, four local Scarlett channels, and future Raven/phone sources:
|
||||
|
||||
- 2 seconds = 384,000 samples per channel.
|
||||
- Four channels = 1.5 million samples per analysis window.
|
||||
- Analysis every 100 ms can easily become a full CPU core if windows are
|
||||
rescanned naively.
|
||||
|
||||
Rules:
|
||||
|
||||
- Incremental streaming state beats full-window rescans.
|
||||
- Precompute chirp kernels.
|
||||
- Dechirp/bin score in batches.
|
||||
- Reuse aligned buffers.
|
||||
- Separate proposal, classification, and global code solving.
|
||||
|
||||
### Video
|
||||
|
||||
Six optical sensors with high-rate Eyes and Leap IR mean:
|
||||
|
||||
- CPU copying/compression/debug JSON is poison in the hot path.
|
||||
- Capture should produce native handles or stable byte spans with timestamps.
|
||||
- GPU work should happen once inside Fensalir, not through throwaway staging
|
||||
formats.
|
||||
|
||||
## Implementation Index
|
||||
|
||||
### Current Code To Read First
|
||||
|
||||
- `src/Mimir.Runtime/MimirRuntime.cs`: runtime loop and witness emission.
|
||||
- `src/Mimir.Runtime/Synchronization/MimirSynchronizationHub.cs`: source/buffer
|
||||
and report authority.
|
||||
- `src/Mimir.Runtime/Synchronization/MimirAudioSynchronizationAnalyzer.cs`:
|
||||
buffer-to-report analyzer.
|
||||
- `src/Mimir.Runtime/Synchronization/MimirChirpBinTimeline.cs`: active decoder.
|
||||
- `src/Mimir.Runtime/Synchronization/MimirChirpBinCalibrationModel.cs`:
|
||||
response/confusion/delay model.
|
||||
- `native/asio_capture/mimir_asio_capture.cpp`: runtime ASIO bridge.
|
||||
- `native/reservoir/src/lib.rs`: native shared-edge reservoir.
|
||||
|
||||
### Current Code To Treat As Temporary
|
||||
|
||||
- `MimirFrameEventProcessStreamSource`: diagnostic JSON-line adapter.
|
||||
- `MimirProcessStreamSource`: bridge/network raw stdout adapter.
|
||||
- WASAPI probe sample transport.
|
||||
- FFmpeg/SRT sender scripts.
|
||||
- Legacy dense chirplet path.
|
||||
|
||||
### Current Code To Preserve Conceptually
|
||||
|
||||
- Descriptor/sample/buffer identity model.
|
||||
- Hub report/state cache authority.
|
||||
- ASIO in-process source shape.
|
||||
- Chirp-bin codebook/calibration separation.
|
||||
- Native reservoir shared-edge invariant.
|
||||
|
||||
## Cut Lines
|
||||
|
||||
- Cut JSON/stdout local camera hot path when concrete direct drivers land.
|
||||
- Cut legacy chirplet path once chirp-bin decoder has equivalent docs/tests and
|
||||
physical path proof.
|
||||
- Cut bridge scripts only after OBS-facing direct output is real.
|
||||
- Cut ad hoc response weighting if a formal path model replaces it.
|
||||
|
||||
## Future Work Index
|
||||
|
||||
1. Streaming chirp-bin decoder state.
|
||||
2. Calibration-weighted physical decode proof.
|
||||
3. Reduced alphabet/high-order codebook physical proof.
|
||||
4. Fractional delay/SRO actuator proof.
|
||||
5. Direct Leap/PS3/Kiyo runtime drivers.
|
||||
6. Native reservoir integration with Fensalir/Faust.
|
||||
7. Raven/phone receiver codebook/schedule local decode.
|
||||
8. GPU visual fusion and Spout2 program video.
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
# Architecture Rumination: Building Mimir Without Jenga
|
||||
|
||||
## Clock
|
||||
|
||||
- User correction received after the first documentation pass: spend the full
|
||||
dedicated pass mapping, researching, distilling, ruminating, and writing
|
||||
sample options.
|
||||
- Active pass start recorded locally: `2026-05-23T23:41:19+01:00`.
|
||||
|
||||
## Prime Cut
|
||||
|
||||
Mimir is not an app that "captures devices." That framing is too small and it
|
||||
drags the architecture toward adapters, UI panels, and pile-of-feeds thinking.
|
||||
Mimir is a realtime field reconstruction machine. Devices are evidence
|
||||
producers. The rolling buffer is the measurement window. Synchronization is the
|
||||
act that makes independent evidence comparable. Output is a negotiated product,
|
||||
not a raw feed.
|
||||
|
||||
The architecture should be judged by whether each subsystem makes field
|
||||
evidence more coherent:
|
||||
|
||||
- Can this block preserve timing?
|
||||
- Can this block preserve identity?
|
||||
- Can this block preserve enough raw information for a later better algorithm?
|
||||
- Can this block be explained as "X owns Y so Z remains true"?
|
||||
|
||||
If not, it is probably scaffolding or residue.
|
||||
|
||||
## Current Machine
|
||||
|
||||
### Runtime
|
||||
|
||||
`MimirRuntime` is a Fensalir runtime with an intentionally empty scene. It owns
|
||||
application cadence, audio witness emission, telemetry, and UI readouts. It
|
||||
does not own driver details. It does not own DSP. It should eventually own less
|
||||
of the calibration renderer too: witness generation can stay controlled by the
|
||||
runtime, but sample production and hot-path synthesis should be native/DSP once
|
||||
the shape is settled.
|
||||
|
||||
### Hub
|
||||
|
||||
`MimirSynchronizationHub` is the correct authority for current runtime belief.
|
||||
It owns source registration, buffer append, analyzer scheduling, report cache,
|
||||
and state smoothing. The strongest invariant here is that UI/telemetry must not
|
||||
invoke analysis. That separation prevents accidental workload explosions.
|
||||
|
||||
### Buffers
|
||||
|
||||
`MimirRollingStreamBuffer` is simple and right: one stream identity, one bounded
|
||||
queue, one edge. It is not the final native memory layout, but the ownership
|
||||
model is sound. The native reservoir should be the lower version of the same
|
||||
idea: one edge, typed views, no per-kind retention.
|
||||
|
||||
### Active Audio Decoder
|
||||
|
||||
`MimirChirpBinTimeline` is the live active sync machine. It is conceptually
|
||||
right because the emitter and receiver share the same codebook and schedule.
|
||||
The current code still has scalar and full-window pressure, but the shape is no
|
||||
longer the wrong dense chirplet wall. It is a controlled receiver:
|
||||
|
||||
- proposal;
|
||||
- dechirp;
|
||||
- bin score;
|
||||
- ambiguity preservation;
|
||||
- code-constrained path selection;
|
||||
- clock fit;
|
||||
- calibration evidence.
|
||||
|
||||
The next coherent cut is not "add more rejection." It is streaming state and
|
||||
calibration-weighted decode that changes the likelihood surface before path
|
||||
selection.
|
||||
|
||||
### Passive Audio Decoder
|
||||
|
||||
`MimirPassiveAudioSynchronizationEstimator` is useful but should be demoted in
|
||||
the mental model. It is a passive delay/drift witness, not canonical time. It
|
||||
can suppress active watermark when confidence is stable. It should not invent
|
||||
timeline anchors.
|
||||
|
||||
### Native ASIO
|
||||
|
||||
`native/asio_capture` is a correct direction: COM and ASIO callbacks stay in
|
||||
native code, C# reads bounded channel blocks. This pattern should repeat for
|
||||
camera drivers.
|
||||
|
||||
### Native Probes
|
||||
|
||||
The probes are research instruments. They should keep telling us what hardware
|
||||
does, but their stdout/JSON shape must not leak into production.
|
||||
|
||||
## The Perfect Machine Shape
|
||||
|
||||
### Audio Spine
|
||||
|
||||
1. Native ASIO capture writes input and loopback channels into native audio
|
||||
blocks with device sample counters.
|
||||
2. Mimir runtime indexes those blocks as stream samples.
|
||||
3. Active/passive analyzers update per-source delay/SRO/response state.
|
||||
4. Faust/native DSP consumes the same blocks plus state and emits aligned stems.
|
||||
5. OBS receives stems/spatial bed; it never sees unsynchronized raw mics as the
|
||||
truth.
|
||||
|
||||
### Visual Spine
|
||||
|
||||
1. Native camera drivers capture frames with device or immediate receipt time.
|
||||
2. Runtime indexes handles into rolling buffers/reservoir.
|
||||
3. Fensalir GPU extracts features/flow/depth/surface/material claims.
|
||||
4. Reservoir preserves rolling visual evidence by time.
|
||||
5. Fensalir emits program video via Spout2.
|
||||
|
||||
### Distributed Spine
|
||||
|
||||
Raven and phones should decode local time from codebook/schedule state. They
|
||||
should report:
|
||||
|
||||
- local anchor observations;
|
||||
- confidence;
|
||||
- response surface;
|
||||
- fitted clock;
|
||||
- transport timestamp only as metadata.
|
||||
|
||||
They should not ask Starfire to bless every sample over the network. Network
|
||||
latency is exactly what the codebook is meant to escape.
|
||||
|
||||
## Things The Current Code Gets Right
|
||||
|
||||
- The app/runtime split is clean.
|
||||
- The UI is not the analyzer.
|
||||
- Stream identity is explicit.
|
||||
- Direct ASIO is in process and sample-bearing.
|
||||
- Calibration model includes magnitude, confusion, phase, delay, and adaptive
|
||||
codebook planning.
|
||||
- Runtime emission can consume the calibration plan.
|
||||
- Native reservoir already encodes the single-edge invariant.
|
||||
|
||||
## Things Still Too Soft
|
||||
|
||||
- Active decoder still rescans windows rather than owning a streaming state.
|
||||
- Physical acoustic decode is not yet robust enough.
|
||||
- The actuator does not exist.
|
||||
- Direct camera runtime drivers do not exist.
|
||||
- Native reservoir is not wired into the C#/Fensalir/Faust flow.
|
||||
- Calibration model is loaded, but not yet proven as the decisive fix for
|
||||
meatspace.
|
||||
- Raven/phone receiver shape is only conceptual.
|
||||
|
||||
## Candidate Cuts
|
||||
|
||||
### Cut 1: Streaming Chirp-Bin Decoder
|
||||
|
||||
Owns:
|
||||
|
||||
- rolling PCM window;
|
||||
- energy proposal ring;
|
||||
- candidate frame cache;
|
||||
- anchor trellis;
|
||||
- clock fit state.
|
||||
|
||||
Reason:
|
||||
|
||||
- Avoid full-window rescans every analysis tick.
|
||||
|
||||
### Cut 2: Calibration-Weighted Likelihood
|
||||
|
||||
Owns:
|
||||
|
||||
- path-specific symbol likelihood from response model;
|
||||
- phase/group-delay correction before code selection;
|
||||
- global delay/bin-shift hypothesis scoring.
|
||||
|
||||
Reason:
|
||||
|
||||
- Meatspace failure is not merely noisy timing. It is path-shaped symbol
|
||||
distortion.
|
||||
|
||||
### Cut 3: Native DSP Actuator
|
||||
|
||||
Owns:
|
||||
|
||||
- fractional delay;
|
||||
- resampler;
|
||||
- SRO control;
|
||||
- phase/group-delay correction;
|
||||
- stem alignment.
|
||||
|
||||
Reason:
|
||||
|
||||
- The runtime estimates state. DSP moves samples.
|
||||
|
||||
### Cut 4: Direct Camera Driver Worker
|
||||
|
||||
Owns:
|
||||
|
||||
- device open/configure;
|
||||
- async read queue;
|
||||
- timestamp preservation;
|
||||
- native payload handle.
|
||||
|
||||
Reason:
|
||||
|
||||
- JSON/stdout cannot be allowed near six-camera hot loops.
|
||||
|
||||
## Rejected Temptations
|
||||
|
||||
- More mode flags in analyzer: the problem is evidence ownership, not a switch
|
||||
shortage.
|
||||
- More outlier rejection before calibration weighting: that hides the response
|
||||
surface instead of learning it.
|
||||
- Routing all sources through process adapters because it is easy to debug:
|
||||
that is how bandwidth dies in a formal suit.
|
||||
- Treating 192 kHz as magic: it improves timing granularity and ultrasonic
|
||||
options only where transducers and path response actually preserve signal.
|
||||
|
||||
67
research/perfect-machine-study-2026-05-23/index.md
Normal file
67
research/perfect-machine-study-2026-05-23/index.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# 2026-05-23 Perfect Machine Study Index
|
||||
|
||||
## Purpose
|
||||
|
||||
This directory is the deeper study pass that sits beside the source map. Use it
|
||||
when the next implementation pass needs more than "where is the file?" and
|
||||
needs the reason a cut exists.
|
||||
|
||||
## Documents
|
||||
|
||||
| File | Use It For |
|
||||
| --- | --- |
|
||||
| `architecture-rumination.md` | System ownership, current machine critique, coherent next cuts. |
|
||||
| `optimization-ledger.md` | Hot loop risks, low-level implementation options, micro-optimization candidates. |
|
||||
| `references.md` | Research links for chirplets, CSS/LoRa, GCC-PHAT, D3D12/FFT/GPU compute. |
|
||||
| `samples/README.md` | Index of sample implementation sketches. |
|
||||
|
||||
## Sample Code
|
||||
|
||||
| Sample | Future Production Target | Question It Answers |
|
||||
| --- | --- | --- |
|
||||
| `samples/StreamingChirpBinDecoderSketch.cs` | `MimirChirpBinStreamingDecoder` or equivalent runtime class | What state removes full-window rescans? |
|
||||
| `samples/Avx2DechirpGoertzelSketch.cpp` | Native CPU scorer for chirp-bin candidates | What does SIMD dechirp/bin scoring look like? |
|
||||
| `samples/ChirpBinScore.compute.hlsl` | Fensalir/D3D12 compute scorer | How would batched candidate/bin scoring map to GPU? |
|
||||
| `samples/FarrowFractionalDelaySketch.cpp` | Faust/native DSP actuator | What is the smallest fractional delay proof? |
|
||||
| `samples/SpscAudioBlockRingSketch.cpp` | Native capture worker handoff | What shape should low-jitter audio block transfer take? |
|
||||
|
||||
## Best Next Implementation Sequence
|
||||
|
||||
1. Build a streaming chirp-bin decoder state object beside the current analyzer,
|
||||
but keep the old analyzer as proof oracle until outputs match.
|
||||
2. Load a real physical mic calibration model and compare weighted versus
|
||||
unweighted symbol likelihood on stored Scarlett captures.
|
||||
3. Emit reduced reliable-bin codebook from the physical model and measure
|
||||
meatspace anchor rate.
|
||||
4. Add native/Faust actuator proof using logged `MimirAudioSynchronizationState`
|
||||
before touching live program output.
|
||||
5. Port one direct camera path into `IMimirVideoCaptureDriverSource`; Leap first
|
||||
if timing/depth value wins, PS3 Eye first if raw driver throughput is the
|
||||
faster proof.
|
||||
|
||||
## Index Terms
|
||||
|
||||
- active sync
|
||||
- ASIO
|
||||
- AVX2
|
||||
- chirp-bin
|
||||
- chirplet
|
||||
- codebook adaptation
|
||||
- dechirp
|
||||
- D3D12 compute
|
||||
- Farrow fractional delay
|
||||
- Faust actuator
|
||||
- GCC-PHAT
|
||||
- group delay
|
||||
- LoRa CSS
|
||||
- native reservoir
|
||||
- passive sync
|
||||
- Raven
|
||||
- response/confusion matrix
|
||||
- sample-rate offset
|
||||
- SPSC ring
|
||||
- Starfire
|
||||
- streaming decoder
|
||||
- volumetric audio
|
||||
- volumetric visual fusion
|
||||
|
||||
174
research/perfect-machine-study-2026-05-23/optimization-ledger.md
Normal file
174
research/perfect-machine-study-2026-05-23/optimization-ledger.md
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# Optimization Ledger
|
||||
|
||||
## What This File Is
|
||||
|
||||
This is the low-level implementation ledger for Mimir's most likely hot paths.
|
||||
It is not a benchmark result. It is a map of what to test, why it might matter,
|
||||
and where the current code is likely to pay unnecessary cost.
|
||||
|
||||
## Hot Path 1: Chirp-Bin Decode
|
||||
|
||||
### Current Shape
|
||||
|
||||
- Build an energy trace across a window.
|
||||
- Classify local candidate windows.
|
||||
- Dechirp and score each bin.
|
||||
- Run code-valid anchor selection.
|
||||
- Fit clock and refine offset.
|
||||
|
||||
### Bottleneck Risk
|
||||
|
||||
The current scalar/classic C# version is correct enough to reason about, but it
|
||||
still encourages repeated full-window work. Six or more audio streams at 192 kHz
|
||||
make that expensive quickly.
|
||||
|
||||
### Preferred Evolution
|
||||
|
||||
1. Streaming energy proposal ring.
|
||||
2. Precomputed chirp kernels by sample rate and symbol plan.
|
||||
3. SIMD dechirp/bin score on CPU for small channel counts.
|
||||
4. Batched GPU compute for many candidate windows or many remote receivers.
|
||||
5. Keep the codebook trellis on CPU unless profiling proves otherwise; it is
|
||||
branchy and small.
|
||||
|
||||
### Micro-Optimization Ideas
|
||||
|
||||
- Structure-of-arrays for sine/cosine kernels.
|
||||
- Process bins in groups of 8 floats on AVX2/FMA.
|
||||
- Keep hot candidate windows contiguous and aligned.
|
||||
- Avoid allocating candidate arrays inside every analysis tick.
|
||||
- Cache symbol reliability weights in dense arrays.
|
||||
- Precompute expected event sample offsets for the active window.
|
||||
- Use a ring of candidate frames keyed by absolute sample index.
|
||||
|
||||
### Sample References
|
||||
|
||||
- `samples/StreamingChirpBinDecoderSketch.cs`
|
||||
- `samples/Avx2DechirpGoertzelSketch.cpp`
|
||||
- `samples/ChirpBinScore.compute.hlsl`
|
||||
|
||||
## Hot Path 2: Passive GCC-PHAT
|
||||
|
||||
### Current Shape
|
||||
|
||||
- Allocate complex arrays per estimate.
|
||||
- FFT reference/candidate.
|
||||
- Normalize cross-spectrum.
|
||||
- Inverse FFT.
|
||||
- Search lags.
|
||||
|
||||
### Bottleneck Risk
|
||||
|
||||
Passive sync is cheaper than dense chirplet matching, but repeated allocations
|
||||
and full FFTs per source pair can still dominate if called too often.
|
||||
|
||||
### Preferred Evolution
|
||||
|
||||
- Reuse FFT buffers/plans.
|
||||
- Keep reference spectrum cached for the current analysis edge.
|
||||
- Batch candidate transforms.
|
||||
- Move to native FFTW/KissFFT/MKL/cuFFT only after measured C# FFT cost matters.
|
||||
- Use passive only as confidence/drift support, not as mandatory every-tick work.
|
||||
|
||||
### Micro-Optimization Ideas
|
||||
|
||||
- Window into preallocated `Complex` spans.
|
||||
- Precompute Hann window.
|
||||
- Preemphasis in one pass with mean removal.
|
||||
- Limit lag search by physical/network horizon.
|
||||
- Use parabolic interpolation only around plausible peaks.
|
||||
|
||||
## Hot Path 3: Fractional Delay And SRO Actuator
|
||||
|
||||
### Current Shape
|
||||
|
||||
- Not built.
|
||||
|
||||
### Preferred Evolution
|
||||
|
||||
- Prototype native/Faust fractional delay using Farrow/Lagrange for small
|
||||
sub-sample corrections.
|
||||
- Add a higher quality polyphase sinc path for program output.
|
||||
- Drive both from smoothed delay/SRO state.
|
||||
- Keep per-source state in DSP, not in UI/runtime strings.
|
||||
|
||||
### Micro-Optimization Ideas
|
||||
|
||||
- Fixed filter order for predictable SIMD.
|
||||
- Interleave channels only where the DSP kernel wants it.
|
||||
- Separate control-rate state update from audio-rate sample processing.
|
||||
- Use denormal guards.
|
||||
|
||||
### Sample Reference
|
||||
|
||||
- `samples/FarrowFractionalDelaySketch.cpp`
|
||||
|
||||
## Hot Path 4: Native Rolling Buffers
|
||||
|
||||
### Current Shape
|
||||
|
||||
- C# rolling buffers store sample envelopes.
|
||||
- Rust reservoir stores one shared-edge native rolling buffer with typed views.
|
||||
|
||||
### Bottleneck Risk
|
||||
|
||||
The C# buffer shape is fine for current proof state, but payload-heavy audio and
|
||||
video should move to native memory handles. Copies and per-sample allocations
|
||||
will become visible as source count rises.
|
||||
|
||||
### Preferred Evolution
|
||||
|
||||
- Native SPSC rings per capture worker feeding a shared reservoir index.
|
||||
- Payload handles point to native/audio/GPU memory owned by capture/DSP/Fensalir.
|
||||
- Runtime stores metadata and current belief.
|
||||
|
||||
### Micro-Optimization Ideas
|
||||
|
||||
- Power-of-two ring capacities.
|
||||
- Single writer per capture device.
|
||||
- Cache-line padded head/tail counters.
|
||||
- Batch publish blocks.
|
||||
- Avoid sharing mutable payload ownership across subsystems.
|
||||
|
||||
### Sample Reference
|
||||
|
||||
- `samples/SpscAudioBlockRingSketch.cpp`
|
||||
|
||||
## Hot Path 5: Camera Capture And GPU Fusion
|
||||
|
||||
### Current Shape
|
||||
|
||||
- Native probes prove direct driver access and cadence.
|
||||
- Runtime direct driver seam exists.
|
||||
- Fensalir fusion not wired yet.
|
||||
|
||||
### Bottleneck Risk
|
||||
|
||||
Six cameras make CPU copies and process bridges fail. Leap/PS3/Kiyo sources
|
||||
need direct capture, stable timestamps, and GPU-friendly payloads.
|
||||
|
||||
### Preferred Evolution
|
||||
|
||||
- Direct KS/libusb/vendor driver workers.
|
||||
- Native payload handles into runtime/reservoir.
|
||||
- Fensalir consumes current window and uploads/processes on GPU.
|
||||
- Use D3D12 shared resources where possible.
|
||||
|
||||
### Micro-Optimization Ideas
|
||||
|
||||
- Queue multiple async reads per camera.
|
||||
- Keep camera buffers pinned/native.
|
||||
- Avoid decode unless the algorithm needs decoded pixels.
|
||||
- Do per-camera feature extraction in compute, then fuse compact features.
|
||||
|
||||
## External References
|
||||
|
||||
- LoRa/CSS receivers repeatedly validate dechirp plus FFT/bin scoring as the
|
||||
natural controlled-chirp demodulator shape.
|
||||
- HLSL Shader Model 6 wave intrinsics are relevant for reductions and FFT-like
|
||||
kernels inside D3D12 compute.
|
||||
- cuFFT callbacks show a general trick: combine preprocessing with transform
|
||||
load/store to avoid extra memory bandwidth.
|
||||
- FFTW wisdom/alignment notes matter if we move passive or chirp-bin batches to
|
||||
native FFT plans.
|
||||
|
||||
70
research/perfect-machine-study-2026-05-23/references.md
Normal file
70
research/perfect-machine-study-2026-05-23/references.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Research References
|
||||
|
||||
## Chirplet And Chirp Analysis
|
||||
|
||||
- Steve Mann, *The Chirplet Transform: A new signal analysis technique based on
|
||||
affine relationships in the time-frequency plane*:
|
||||
https://www.media.mit.edu/publications/the-chirplet-transform-a-new-signal-analysis-technique-based-on-affine-relationships-in-the-time-frequency-plane/
|
||||
- General linear chirplet transform overview:
|
||||
https://www.sciencedirect.com/science/article/pii/S0888327015003994
|
||||
- High-resolution chirplet transform parameter analysis:
|
||||
https://arxiv.org/abs/2108.00572
|
||||
|
||||
## Chirp Spread Spectrum / LoRa Receiver Shape
|
||||
|
||||
- SDR-LoRa design notes, including synchronization via dechirp and FFT:
|
||||
https://www.sciencedirect.com/science/article/pii/S1389128624000264
|
||||
- OpenLoRa demodulator evaluation:
|
||||
https://openlora.wisc.edu/demodulators-evaluated/
|
||||
- OpenLoRa NSDI paper:
|
||||
https://www.usenix.org/system/files/nsdi23-mishra.pdf
|
||||
- CSS receiver design:
|
||||
https://arxiv.org/abs/2105.02833
|
||||
- LoRa/CSS tutorial:
|
||||
https://arxiv.org/abs/2310.10503
|
||||
- I/Q CSS with coherent detector and channel estimation:
|
||||
https://arxiv.org/abs/2009.10421
|
||||
- Data-over-audio reference implementation trail:
|
||||
https://github.com/cawfree/OpenChirp
|
||||
|
||||
## Passive Audio Timing / GCC-PHAT
|
||||
|
||||
- Improved GCC-PHAT weighting for time-delay estimation:
|
||||
https://pmc.ncbi.nlm.nih.gov/articles/PMC9571281/
|
||||
- Acoustic DoA paper noting GCC-PHAT normalization and interpolation:
|
||||
https://pmc.ncbi.nlm.nih.gov/articles/PMC11014389/
|
||||
- Parametrized GCC-PHAT features for time-delay estimation:
|
||||
https://www.isca-archive.org/interspeech_2021/salvati21_interspeech.html
|
||||
- Complexity/accuracy of GCC-PHAT methods:
|
||||
https://arxiv.org/abs/1811.11787
|
||||
|
||||
## Low-Level Transform / Compute References
|
||||
|
||||
- HLSL Shader Model 6 wave intrinsics:
|
||||
https://learn.microsoft.com/windows/win32/direct3dhlsl/hlsl-shader-model-6-0-features-for-direct3d-12
|
||||
- DirectX Shader Compiler wave intrinsics notes:
|
||||
https://github.com/microsoft/directxshadercompiler/wiki/wave-intrinsics
|
||||
- cuFFT callback documentation:
|
||||
https://docs.nvidia.com/cuda/archive/12.5.0/cufft/index.html
|
||||
- NVIDIA cuFFT callback performance article:
|
||||
https://developer.nvidia.com/blog/cuda-pro-tip-use-cufft-callbacks-custom-data-processing/
|
||||
- FFTW reference:
|
||||
https://www.fftw.org/fftw2_doc/fftw_3.html
|
||||
- FFTW SIMD alignment:
|
||||
https://www.fftw.org/~fftw/doc/SIMD-alignment-and-fftw_005fmalloc.html
|
||||
- Chirp Z-transform theory:
|
||||
https://pyffs.readthedocs.io/en/stable/theory/CZT.html
|
||||
|
||||
## How These References Steer Mimir
|
||||
|
||||
- Dechirp-plus-bin scoring is the right hot active receiver shape for controlled
|
||||
chirps.
|
||||
- Dense generic chirplet transforms are research/reference tools, not the
|
||||
runtime hot loop unless the codebook stops being controlled.
|
||||
- GCC-PHAT is still valuable for passive relative delay, but it must remain an
|
||||
evidence source with confidence, not a canonical timestamp source.
|
||||
- GPU or native FFT paths should be considered only where batching is real; for
|
||||
a small fixed bin bank, SIMD Goertzel/dechirp may beat FFT overhead.
|
||||
- If a transform path moves to GPU, fuse dechirp/window/preprocess with transform
|
||||
load/store where possible to avoid memory bandwidth waste.
|
||||
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// Sketch only. Compile-time target would be /arch:AVX2 plus FMA.
|
||||
// This illustrates a CPU hot loop for dechirp + small-bin scoring.
|
||||
|
||||
#include <immintrin.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
struct KernelSoA
|
||||
{
|
||||
const float* cosBase;
|
||||
const float* sinBase;
|
||||
const float* binCos;
|
||||
const float* binSin;
|
||||
int sampleCount;
|
||||
int binCount;
|
||||
};
|
||||
|
||||
struct BinScore
|
||||
{
|
||||
float real;
|
||||
float imag;
|
||||
float energy;
|
||||
};
|
||||
|
||||
static inline float hsum256(__m256 value)
|
||||
{
|
||||
__m128 low = _mm256_castps256_ps128(value);
|
||||
__m128 high = _mm256_extractf128_ps(value, 1);
|
||||
__m128 sum = _mm_add_ps(low, high);
|
||||
sum = _mm_hadd_ps(sum, sum);
|
||||
sum = _mm_hadd_ps(sum, sum);
|
||||
return _mm_cvtss_f32(sum);
|
||||
}
|
||||
|
||||
void score_dechirped_bins_avx2(
|
||||
const float* samples,
|
||||
const KernelSoA& kernel,
|
||||
BinScore* outScores)
|
||||
{
|
||||
for (int bin = 0; bin < kernel.binCount; ++bin)
|
||||
{
|
||||
__m256 accRe = _mm256_setzero_ps();
|
||||
__m256 accIm = _mm256_setzero_ps();
|
||||
|
||||
const float* binCos = kernel.binCos + bin * kernel.sampleCount;
|
||||
const float* binSin = kernel.binSin + bin * kernel.sampleCount;
|
||||
|
||||
int i = 0;
|
||||
for (; i + 7 < kernel.sampleCount; i += 8)
|
||||
{
|
||||
__m256 x = _mm256_loadu_ps(samples + i);
|
||||
__m256 chirpC = _mm256_loadu_ps(kernel.cosBase + i);
|
||||
__m256 chirpS = _mm256_loadu_ps(kernel.sinBase + i);
|
||||
__m256 toneC = _mm256_loadu_ps(binCos + i);
|
||||
__m256 toneS = _mm256_loadu_ps(binSin + i);
|
||||
|
||||
// Real input multiplied by conjugate chirp, then projected onto
|
||||
// the candidate bin tone. Algebra can be fused further once the
|
||||
// final kernel convention is frozen.
|
||||
__m256 deRe = _mm256_mul_ps(x, chirpC);
|
||||
__m256 deIm = _mm256_mul_ps(x, _mm256_sub_ps(_mm256_setzero_ps(), chirpS));
|
||||
|
||||
accRe = _mm256_fmadd_ps(deRe, toneC, accRe);
|
||||
accRe = _mm256_fnmadd_ps(deIm, toneS, accRe);
|
||||
accIm = _mm256_fmadd_ps(deRe, toneS, accIm);
|
||||
accIm = _mm256_fmadd_ps(deIm, toneC, accIm);
|
||||
}
|
||||
|
||||
float re = hsum256(accRe);
|
||||
float im = hsum256(accIm);
|
||||
for (; i < kernel.sampleCount; ++i)
|
||||
{
|
||||
float deRe = samples[i] * kernel.cosBase[i];
|
||||
float deIm = -samples[i] * kernel.sinBase[i];
|
||||
re += deRe * binCos[i] - deIm * binSin[i];
|
||||
im += deRe * binSin[i] + deIm * binCos[i];
|
||||
}
|
||||
|
||||
outScores[bin] = { re, im, re * re + im * im };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Sketch only. D3D12 compute shape for batched chirp-bin scoring.
|
||||
// One thread group scores one candidate window against one symbol/bin block.
|
||||
|
||||
cbuffer ChirpScoreConstants : register(b0)
|
||||
{
|
||||
uint SampleCount;
|
||||
uint BinCount;
|
||||
uint CandidateCount;
|
||||
uint KernelStride;
|
||||
};
|
||||
|
||||
StructuredBuffer<float> Samples : register(t0);
|
||||
StructuredBuffer<uint> CandidateSampleOffsets : register(t1);
|
||||
StructuredBuffer<float2> DechirpKernel : register(t2);
|
||||
StructuredBuffer<float2> BinKernel : register(t3);
|
||||
RWStructuredBuffer<float4> Scores : register(u0);
|
||||
|
||||
groupshared float2 Partial[256];
|
||||
|
||||
[numthreads(256, 1, 1)]
|
||||
void main(uint3 groupId : SV_GroupID, uint groupIndex : SV_GroupIndex)
|
||||
{
|
||||
uint candidate = groupId.x;
|
||||
uint bin = groupId.y;
|
||||
uint offset = CandidateSampleOffsets[candidate];
|
||||
|
||||
float2 acc = float2(0.0, 0.0);
|
||||
for (uint i = groupIndex; i < SampleCount; i += 256)
|
||||
{
|
||||
float x = Samples[offset + i];
|
||||
float2 chirp = DechirpKernel[i];
|
||||
float2 tone = BinKernel[bin * KernelStride + i];
|
||||
|
||||
float2 dechirped = float2(x * chirp.x, x * chirp.y);
|
||||
acc += float2(
|
||||
dechirped.x * tone.x - dechirped.y * tone.y,
|
||||
dechirped.x * tone.y + dechirped.y * tone.x);
|
||||
}
|
||||
|
||||
Partial[groupIndex] = acc;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
for (uint stride = 128; stride > 0; stride >>= 1)
|
||||
{
|
||||
if (groupIndex < stride)
|
||||
{
|
||||
Partial[groupIndex] += Partial[groupIndex + stride];
|
||||
}
|
||||
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
|
||||
if (groupIndex == 0)
|
||||
{
|
||||
float2 z = Partial[0];
|
||||
float energy = dot(z, z);
|
||||
Scores[candidate * BinCount + bin] = float4(z.x, z.y, energy, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Sketch only. Cubic Farrow fractional-delay actuator for small delay changes.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
class FarrowFractionalDelay
|
||||
{
|
||||
public:
|
||||
float process(float x, double delaySamples)
|
||||
{
|
||||
push(x);
|
||||
|
||||
int whole = static_cast<int>(std::floor(delaySamples));
|
||||
double mu = delaySamples - whole;
|
||||
|
||||
float xm1 = read(whole + 3);
|
||||
float x0 = read(whole + 2);
|
||||
float x1 = read(whole + 1);
|
||||
float x2 = read(whole + 0);
|
||||
|
||||
// Cubic Lagrange form. Good enough for an actuator proof; production
|
||||
// should compare against polyphase sinc for final program quality.
|
||||
double c0 = x0;
|
||||
double c1 = 0.5 * (x1 - xm1);
|
||||
double c2 = xm1 - 2.5 * x0 + 2.0 * x1 - 0.5 * x2;
|
||||
double c3 = 0.5 * (x2 - xm1) + 1.5 * (x0 - x1);
|
||||
return static_cast<float>(((c3 * mu + c2) * mu + c1) * mu + c0);
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<float, 4096> ring{};
|
||||
unsigned cursor = 0;
|
||||
|
||||
void push(float x)
|
||||
{
|
||||
ring[cursor++ & (ring.size() - 1)] = x;
|
||||
}
|
||||
|
||||
float read(int delay) const
|
||||
{
|
||||
return ring[(cursor - 1 - static_cast<unsigned>(delay)) & (ring.size() - 1)];
|
||||
}
|
||||
};
|
||||
|
||||
22
research/perfect-machine-study-2026-05-23/samples/README.md
Normal file
22
research/perfect-machine-study-2026-05-23/samples/README.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Sample Implementation Sketches
|
||||
|
||||
These are not production files. They are indexed sketches for future cuts,
|
||||
written to make implementation options concrete without sneaking half-built
|
||||
machinery into the app.
|
||||
|
||||
## Files
|
||||
|
||||
- `StreamingChirpBinDecoderSketch.cs`: C# state model for an incremental
|
||||
chirp-bin decoder that avoids full-window rescans.
|
||||
- `Avx2DechirpGoertzelSketch.cpp`: AVX2/FMA-shaped CPU scoring kernel sketch.
|
||||
- `ChirpBinScore.compute.hlsl`: D3D12 compute shader shape for batched
|
||||
candidate/bin scoring.
|
||||
- `FarrowFractionalDelaySketch.cpp`: native fractional delay actuator sketch.
|
||||
- `SpscAudioBlockRingSketch.cpp`: native single-producer/single-consumer audio
|
||||
block ring sketch for capture worker handoff.
|
||||
|
||||
## Rule
|
||||
|
||||
Promote one of these only after a benchmark or integration test proves it owns a
|
||||
real invariant. Sample code is a microscope, not a new wall in the house.
|
||||
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Sketch only. Native capture handoff ring for one producer and one consumer.
|
||||
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
struct AudioBlockHandle
|
||||
{
|
||||
std::uint64_t dataHandle;
|
||||
std::uint64_t timestampNs;
|
||||
std::uint64_t sequence;
|
||||
std::uint32_t frameCount;
|
||||
std::uint32_t sampleRate;
|
||||
std::uint32_t channelCount;
|
||||
std::uint32_t format;
|
||||
};
|
||||
|
||||
template <std::size_t CapacityPowerOfTwo>
|
||||
class SpscAudioBlockRing
|
||||
{
|
||||
public:
|
||||
static_assert((CapacityPowerOfTwo & (CapacityPowerOfTwo - 1)) == 0);
|
||||
|
||||
bool push(const AudioBlockHandle& block)
|
||||
{
|
||||
auto head = head_.load(std::memory_order_relaxed);
|
||||
auto next = head + 1;
|
||||
if (next - tail_.load(std::memory_order_acquire) > CapacityPowerOfTwo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
blocks_[head & (CapacityPowerOfTwo - 1)] = block;
|
||||
head_.store(next, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pop(AudioBlockHandle& block)
|
||||
{
|
||||
auto tail = tail_.load(std::memory_order_relaxed);
|
||||
if (tail == head_.load(std::memory_order_acquire))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
block = blocks_[tail & (CapacityPowerOfTwo - 1)];
|
||||
tail_.store(tail + 1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
alignas(64) std::atomic<std::uint64_t> head_{0};
|
||||
alignas(64) std::atomic<std::uint64_t> tail_{0};
|
||||
std::array<AudioBlockHandle, CapacityPowerOfTwo> blocks_{};
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
// Sketch only. This shows the state shape for an incremental chirp-bin decoder.
|
||||
// It is not compiled by Mimir.
|
||||
|
||||
namespace Mimir.Research.Samples;
|
||||
|
||||
public sealed class StreamingChirpBinDecoderSketch
|
||||
{
|
||||
private readonly ChirpBinPlan plan;
|
||||
private readonly Ring<float> pcm;
|
||||
private readonly Ring<float> energy;
|
||||
private readonly List<CandidateFrame> candidates = [];
|
||||
private readonly List<TimelineAnchor> anchors = [];
|
||||
private long absoluteSampleCursor;
|
||||
private long lastScannedSample;
|
||||
|
||||
public StreamingChirpBinDecoderSketch(ChirpBinPlan plan, int maxWindowSamples)
|
||||
{
|
||||
this.plan = plan;
|
||||
pcm = new Ring<float>(NextPowerOfTwo(maxWindowSamples));
|
||||
energy = new Ring<float>(NextPowerOfTwo(maxWindowSamples / plan.HopSamples + 8));
|
||||
}
|
||||
|
||||
public void Append(ReadOnlySpan<float> samples)
|
||||
{
|
||||
foreach (var sample in samples)
|
||||
{
|
||||
pcm.Push(sample);
|
||||
absoluteSampleCursor++;
|
||||
}
|
||||
}
|
||||
|
||||
public DecodeSnapshot DecodeAvailable()
|
||||
{
|
||||
ScanEnergyIncrementally();
|
||||
ClassifyNewProposals();
|
||||
PruneOldCandidates();
|
||||
SolveAnchorsIncrementally();
|
||||
var fit = FitClock(anchors);
|
||||
return new DecodeSnapshot([.. candidates], [.. anchors], fit);
|
||||
}
|
||||
|
||||
private void ScanEnergyIncrementally()
|
||||
{
|
||||
var firstPossible = lastScannedSample;
|
||||
var lastPossible = absoluteSampleCursor - plan.WindowSamples;
|
||||
for (var start = firstPossible; start <= lastPossible; start += plan.HopSamples)
|
||||
{
|
||||
var window = pcm.ReadAbsolute(start, plan.WindowSamples);
|
||||
energy.Push(Rms(window));
|
||||
if (energy.Latest > plan.EnergyThreshold)
|
||||
{
|
||||
candidates.Add(new CandidateFrame(start, Energy: energy.Latest));
|
||||
}
|
||||
}
|
||||
|
||||
lastScannedSample = Math.Max(lastScannedSample, lastPossible);
|
||||
}
|
||||
|
||||
private void ClassifyNewProposals()
|
||||
{
|
||||
foreach (var candidate in candidates.Where(candidate => candidate.Symbols.Length == 0))
|
||||
{
|
||||
var window = pcm.ReadAbsolute(candidate.AbsoluteSample, plan.WindowSamples);
|
||||
candidate.Symbols = plan.Score(window);
|
||||
}
|
||||
}
|
||||
|
||||
private void SolveAnchorsIncrementally()
|
||||
{
|
||||
// Production version should use a bounded trellis over the new tail,
|
||||
// not rebuild the entire anchor path.
|
||||
var tail = candidates
|
||||
.Where(candidate => candidate.Symbols.Length > 0)
|
||||
.OrderBy(candidate => candidate.AbsoluteSample)
|
||||
.TakeLast(plan.TrellisTailFrames)
|
||||
.ToArray();
|
||||
|
||||
foreach (var triple in ConsecutiveTriples(tail))
|
||||
{
|
||||
if (!plan.TryMapTriplet(triple, out var eventIndex, out var confidence))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
anchors.Add(new TimelineAnchor(
|
||||
eventIndex,
|
||||
triple[1].AbsoluteSample,
|
||||
confidence));
|
||||
}
|
||||
}
|
||||
|
||||
private void PruneOldCandidates()
|
||||
{
|
||||
var keepAfter = absoluteSampleCursor - pcm.Capacity;
|
||||
candidates.RemoveAll(candidate => candidate.AbsoluteSample < keepAfter);
|
||||
anchors.RemoveAll(anchor => anchor.ObservedSample < keepAfter);
|
||||
}
|
||||
|
||||
private static double Rms(ReadOnlySpan<float> samples)
|
||||
{
|
||||
var sum = 0.0;
|
||||
foreach (var sample in samples)
|
||||
{
|
||||
sum += sample * sample;
|
||||
}
|
||||
|
||||
return Math.Sqrt(sum / Math.Max(1, samples.Length));
|
||||
}
|
||||
|
||||
private static IEnumerable<CandidateFrame[]> ConsecutiveTriples(CandidateFrame[] frames)
|
||||
{
|
||||
for (var index = 0; index + 2 < frames.Length; index++)
|
||||
{
|
||||
yield return [frames[index], frames[index + 1], frames[index + 2]];
|
||||
}
|
||||
}
|
||||
|
||||
private static ClockFit? FitClock(IReadOnlyList<TimelineAnchor> anchors) => null;
|
||||
|
||||
private static int NextPowerOfTwo(int value)
|
||||
{
|
||||
var power = 1;
|
||||
while (power < value)
|
||||
{
|
||||
power <<= 1;
|
||||
}
|
||||
|
||||
return power;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record ChirpBinPlan(
|
||||
int WindowSamples,
|
||||
int HopSamples,
|
||||
int TrellisTailFrames,
|
||||
double EnergyThreshold,
|
||||
Func<ReadOnlySpan<float>, SymbolScore[]> Score,
|
||||
TryMapTripletDelegate TryMapTriplet);
|
||||
|
||||
public delegate bool TryMapTripletDelegate(
|
||||
IReadOnlyList<CandidateFrame> frames,
|
||||
out ulong eventIndex,
|
||||
out double confidence);
|
||||
|
||||
public sealed record CandidateFrame(long AbsoluteSample, double Energy)
|
||||
{
|
||||
public SymbolScore[] Symbols { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed record SymbolScore(int SymbolId, double Score, double FractionalOffset);
|
||||
|
||||
public sealed record TimelineAnchor(ulong EventIndex, long ObservedSample, double Confidence);
|
||||
|
||||
public sealed record ClockFit(double SourceOffsetSamples, double SampleRate, double Confidence);
|
||||
|
||||
public sealed record DecodeSnapshot(
|
||||
IReadOnlyList<CandidateFrame> Candidates,
|
||||
IReadOnlyList<TimelineAnchor> Anchors,
|
||||
ClockFit? ClockFit);
|
||||
|
||||
public sealed class Ring<T>
|
||||
{
|
||||
private readonly T[] data;
|
||||
private long cursor;
|
||||
|
||||
public Ring(int capacity)
|
||||
{
|
||||
data = new T[capacity];
|
||||
}
|
||||
|
||||
public int Capacity => data.Length;
|
||||
|
||||
public T Latest => data[(cursor - 1) & (data.Length - 1)];
|
||||
|
||||
public void Push(T value)
|
||||
{
|
||||
data[cursor & (data.Length - 1)] = value;
|
||||
cursor++;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> ReadAbsolute(long absoluteStart, int count)
|
||||
{
|
||||
// Sketch simplification: production should copy wraparound into a
|
||||
// scratch span or expose two spans.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue