mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 03:09:57 +00:00
feat(highway): render per-note bend curve (bnv) on 2D + 3D (#532)
PR-B of the bend-shape feature (feedpak §6.2.1). Both highways drew a bend
from the scalar `bn` only; now they trace the authoritative `bnv` curve
([{t, v}]) when present and fall back to the `bn` arc/envelope otherwise.
2D (static/highway.js drawNote): when a note carries `bnv`, draw the real
shape as a contour above the gem (round-trip rises then falls, pre-bend
starts high, release descends — `bt` is implicit in the point shape), with
an arrowhead only when the gesture ends rising. `bnvNormalizedPoints` maps
{t,v} to a 0..1 x span. The scalar-arrow path is preserved unchanged as the
fallback; the peak label is unchanged.
3D (plugins/highway_3d/screen.js): `bnvSampleAt` linearly interpolates the
curve (clamped to its endpoints) and `bendSemisAtTime` samples it when
present, else keeps the synthetic rise→hold→release envelope from `bn`. The
chevron count still comes from the peak. Fixed a stale-scratch hazard: the
reused `_scrChordNote` now resets `bnv`/`bt` (omit-when-default) after
Object.assign, mirroring the existing `fhm` reset, so a chord note without a
curve can't inherit the previous note's contour.
Render-only — no wire/schema change. Pure helpers covered by
tests/js/highway_bend_curve.test.js (interp, clamping, round-trip,
degenerate/empty); node --check passes on both files; full tests/js green.
Part of got-feedback/feedback#334
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e33df9a720
commit
351b273ab5
@@ -9823,6 +9823,13 @@
|
||||
// so Object.assign leaves a stale `true` from a previous
|
||||
// muted chord note untouched. Reset it explicitly here.
|
||||
_scrChordNote.fhm = cn.fhm || false;
|
||||
// Same stale-scratch hazard for the bend shape:
|
||||
// `bnv`/`bt` are omit-when-default on the wire, so a
|
||||
// chord note without them would otherwise inherit the
|
||||
// previous note's curve (and bendSemisAtTime would
|
||||
// apply the wrong contour). Reset explicitly.
|
||||
_scrChordNote.bnv = Array.isArray(cn.bnv) ? cn.bnv : undefined;
|
||||
_scrChordNote.bt = cn.bt || 0;
|
||||
drawNote(
|
||||
_scrChordNote,
|
||||
now,
|
||||
@@ -11309,15 +11316,40 @@
|
||||
return visualIdx >= (nStr - 1) * 0.5 ? -1 : 1;
|
||||
}
|
||||
|
||||
function bnvSampleAt(bnv, t) {
|
||||
// Linear interpolation of a bend curve [{t, v}] (§6.2.1; t is
|
||||
// seconds from the note onset) at elapsed time t. Clamps to the
|
||||
// endpoints; returns 0 for an empty/invalid curve.
|
||||
if (!Array.isArray(bnv) || bnv.length === 0) return 0;
|
||||
if (t <= bnv[0].t) return bnv[0].v;
|
||||
const last = bnv[bnv.length - 1];
|
||||
if (t >= last.t) return last.v;
|
||||
for (let i = 1; i < bnv.length; i++) {
|
||||
const a = bnv[i - 1], b = bnv[i];
|
||||
if (t <= b.t) {
|
||||
const span = b.t - a.t;
|
||||
return span > 0 ? a.v + (b.v - a.v) * ((t - a.t) / span) : b.v;
|
||||
}
|
||||
}
|
||||
return last.v;
|
||||
}
|
||||
|
||||
function bendSemisAtTime(n, chartTime) {
|
||||
if (!(n?.sus > 0)) return 0;
|
||||
// When the note carries an authoritative bend curve (§6.2.1),
|
||||
// sample its real shape at the elapsed time so the gem's Y gesture
|
||||
// and sustain ribbon follow the actual bend (pre-bend, round-trip,
|
||||
// release, …). Negative samples clamp to 0 (upward-only Y offset).
|
||||
if (Array.isArray(n.bnv) && n.bnv.length) {
|
||||
return Math.max(0, bnvSampleAt(n.bnv, chartTime - n.t));
|
||||
}
|
||||
const bn = Number(n?.bn) || 0;
|
||||
if (!(bn > 0) || !(n?.sus > 0)) return 0;
|
||||
if (!(bn > 0)) return 0;
|
||||
const p = Math.max(0, Math.min(1, (chartTime - n.t) / Math.max(n.sus, 1e-6)));
|
||||
// rise → hold → release: ramp up over the first ~35 %, hold, then
|
||||
// release back down over the last ~30 %. Depicts the bend gesture
|
||||
// (up and back down) rather than a monotone climb that only ever
|
||||
// showed the bend going up. Drives both the sustain ribbon's Y
|
||||
// contour and the gem's techniqueYNow offset.
|
||||
// Fallback: synthesize rise → hold → release from the scalar peak.
|
||||
// Ramp up over the first ~35 %, hold, then release over the last
|
||||
// ~30 % — the bend gesture rather than a monotone climb. Drives both
|
||||
// the sustain ribbon's Y contour and the gem's techniqueYNow offset.
|
||||
const RISE = BEND_ENV_RISE_FRAC, REL = BEND_ENV_RELEASE_FRAC;
|
||||
let env;
|
||||
if (p < RISE) env = p / RISE;
|
||||
|
||||
Reference in New Issue
Block a user