dsp-process 0.3.1

Composable no_std DSP processing traits and adapters for split config/state pipelines
Documentation
# `dsp-process`

Small `no_std` traits for static DSP composition. State, layout, and loop shape
remain explicit; no allocation or dynamic dispatch is required.

## Processing shapes

| Shape | Representation | Use |
| --- | --- | --- |
| one config, one state | `Split<C, S>` | ordinary stateful filter |
| one config, many states | `Split<C, S>::lanes()` | shared coefficients |
| many configs, one state | direct `SplitProcess` calls | prediction/correction phases |
| many config/state pairs | tuples, arrays, `Minor`, `Major` | static pipelines |

`SplitProcess<X, Y, S>` is the primitive: `&self` is immutable configuration;
`&mut S` is caller-owned state. Different configurations may therefore operate
on the same state:

```rust
use dsp_process::SplitProcess;

struct State(i32);
struct Predict(i32);
struct Correct;

impl SplitProcess<(), (), State> for Predict {
    fn process(&self, state: &mut State, (): ()) {
        state.0 += self.0;
    }
}

impl SplitProcess<i32, i32, State> for Correct {
    fn process(&self, state: &mut State, measurement: i32) -> i32 {
        state.0 = (state.0 + measurement) / 2;
        state.0
    }
}

let mut state = State(2);
Predict(3).process(&mut state, ());
assert_eq!(Correct.process(&mut state, 9), 7);
```

This is useful when processing has distinct phases but one state, such as a
Kalman transition and observation. Tuple composition instead gives each stage
its own state.

## Owned processors

`Split<C, S>` binds one configuration to one state and implements `Process`:

```rust
use dsp_process::{Offset, Process, Split};

let mut offset = Split::stateless(Offset(3));
assert_eq!(offset.process(5), 8);
```

One configuration can drive several independent states:

```rust
use dsp_process::{Offset, Process, Split};

let mut lanes = Split::stateless(Offset(3)).lanes::<2>();
assert_eq!(lanes.process([1, 10]), [4, 13]);
```

## Composition

Tuples and arrays form serial pipelines. `Parallel` forms branches. `Minor` and
`Major` select loop nesting and scratch placement.

```rust
use dsp_process::{Gain, Offset, Process, Split};

let mut pipeline = (Split::stateless(Offset(3)) * Split::stateless(Gain(4))).minor();
assert_eq!(pipeline.process(5), 32);
```

Adapters (`Chunk`, `ChunkIn`, `ChunkOut`, `Interpolator`, `Decimator`, `Map`)
change rate or call shape without hiding state.

## Layout

Layout-sensitive processing uses typed views. `FrameMajor` and `LaneMajor`
make the physical interpretation part of the type; sample newtypes do not.

```rust
use dsp_process::{LaneMajor, Offset, Split, View, ViewMut, ViewProcess};

let mut lanes = Split::stateless(Offset(3)).lanes::<2>();
let input = View::<_, LaneMajor, 2>::from_flat(&[1, 2, 3, 10, 20, 30], 3);
let mut output = [0; 6];
let output = ViewMut::<_, LaneMajor, 2>::from_flat(&mut output, 3);
lanes.process_view(input, output);
```

Use `Split::per_frame()` to apply a chunk processor to each frame of a
frame-major view.

## Implementing a stage

- Implement `SplitProcess` when configuration and state are distinct.
- Implement `Process` when one value naturally owns both.
- Implement `SplitInplace` or `Inplace` for a real in-place specialization.
- Override `block()` only to improve the loop or memory traffic.

Runtime fields hold values; const generics encode shape; wrappers encode
composition and layout.