Skip to main content

find_knee

Function find_knee 

Source
pub fn find_knee(start: f64, amp: &[f64], clear: usize) -> Option<f64>
Expand description

The knee of a level track: the split where two lines fit best against one, at the crossing of the left line with the release’s first frames.

The left line spans up to KNEE_LEFT frames before the split, the right one up to KNEE_RIGHT after it, and the crossing takes the line over the first KNEE_SIDE_MIN frames after the split.

amp holds the linear amplitude, one value per HOP from start. Only its first clear frames show this note alone, so the rise and the read of the fall stop there. A later corner past them can still replace an earlier one, since a note can be held past the onset of the next, unless the level has fallen under KNEE_FLOOR of the earlier corner’s level over their last frames and comes back before the later corner, by KNEE_BACK: that note has ended, and the later corner belongs to the next note. Every split leaves at least KNEE_SIDE_MIN frames on each side. A split counts when the right line is steeper by KNEE_STEEPEN times the left side’s mean per second, falls by KNEE_FALL times that mean over its side, and the two lines leave at most 1 - KNEE_GAIN of the squared error of one line. Splits that count in a row form a corner, and within a corner the split with the largest gain wins. A later corner replaces an earlier one, by KNEE_BEND and KNEE_RISE. The lines are local, so a decay earlier in the note does not hide its note-off. None when no split counts: a pluck that decays at one rate has no note-off to see.

A level held at 1 for 0.5 s that then falls linearly to 0 over 0.2 s has its knee where the fall starts:

use sound_to_patch::estimate::envelope::find_knee;

let amp: Vec<f64> = (0..800)
    .map(|i| (1.0 - (f64::from(i) - 500.0).max(0.0) / 200.0).max(0.0))
    .collect();
let knee = find_knee(0.0, &amp, usize::MAX);
assert!(knee.is_some_and(|t| (t - 0.5).abs() < 0.002));