first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user