first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# `algorithms/energy-odf`
## Purpose
Energy onset-detection function (ODF) — the cheapest of the three algorithms
shipped by `@suaveplan/audio-analysis-beat`. It consumes a per-block energy
sequence and emits a 1-D curve whose peaks mark sample positions where the
signal's energy rises sharply, i.e. note onsets.
Energy ODF works extremely well on percussive material (kick, snare, claps)
and is the only path that adds essentially no cost over a plain document scan
— per-block energies can be reused from the host's existing block statistics
pipeline rather than recomputed.
## Features
- `blockEnergy(block)` — mean per-sample energy of a single multi-channel
block. Defensive against empty blocks, ragged channel arrays, and
zero-length data.
- `energyOdf(energies)` — half-wave-rectified first difference of an energy
sequence. Same length as the input; entry 0 is always zero.
- `energyOdfFromSamples(channels, blockSize)` — convenience that splits raw
channel data into `blockSize`-sample blocks, computes block energies, and
returns the ODF in one call.
## Usage: Basic Example
```ts
import { energyOdf } from "@suaveplan/audio-analysis-beat/algorithms/energy-odf";
const energies = new Float64Array([0, 0, 0.5, 0.6, 0.1, 0.05, 0.4]);
const odf = energyOdf(energies);
// odf === Float64Array([0, 0, 0.5, 0.1, 0, 0, 0.35])
```
## Usage: Advanced Example
```ts
import {
blockEnergy,
energyOdfFromSamples,
} from "@suaveplan/audio-analysis-beat/algorithms/energy-odf";
const sampleRate = 48_000;
const blockSize = 1024;
const left = new Float32Array(sampleRate * 4); // 4 seconds mono-ish
const right = new Float32Array(sampleRate * 4);
// ... fill ...
const odf = energyOdfFromSamples([left, right], blockSize);
const hopSeconds = blockSize / sampleRate;
// Pick the loudest block in the first second:
let bestIdx = 0;
let best = 0;
for (let i = 0; i < Math.floor(1 / hopSeconds); i++) {
if (odf[i] > best) {
best = odf[i];
bestIdx = i;
}
}
console.log("onset at", bestIdx * hopSeconds, "seconds");
console.log("avg first-block energy", blockEnergy([left.subarray(0, blockSize)]));
```
## API Reference
- `blockEnergy(block: readonly Float32Array[]): number` — mean per-sample
energy summed across channels then divided by `length * channels`.
- `energyOdf(energies: Float64Array): Float64Array` — same-length output;
`out[i] = max(0, energies[i] - energies[i-1])`, `out[0] === 0`.
- `energyOdfFromSamples(channels: readonly Float32Array[], blockSize: number):
Float64Array` — combined pipeline; returns an empty `Float64Array` when
`blockSize <= 0` or the first channel is empty.
## Implementation Notes
The half-wave rectification (`d > 0 ? d : 0`) is the entire reason this works
as an onset detector rather than a generic energy-change detector — falling
edges (notes ending) carry no perceptual onset and zeroing them out keeps the
ODF interpretable as a peak-picking problem.
Mean-per-sample energy rather than raw sum is used because the final block of
a document is often shorter than `blockSize`; without normalisation the last
block would appear artificially quiet and the ODF would spike negative
(rectified to zero), masking a legitimate onset at the end of the track.
Defensive `?? 0` and `?? undefined`-shape guards thread through every loop so
malformed input — an empty channels array, a ragged channels array, a block
whose first channel is undefined — yields a defined zero rather than throwing.
This module is the leaf any of the three algorithm paths can fall back to and
must never propagate an exception of its own.