resampleFutoTrace function
Converts a touch trace to the encoder's features input, [2, 64].
This reproduces the NumPy preprocessing used for the published validation:
linear interpolation in time to about 60 Hz, then by index to 64 samples.
A trace with no duration skips the 60 Hz stage. The arithmetic follows
numpy.linspace and numpy.interp so float32 results match exactly.
Implementation
Float32List resampleFutoTrace(List<FutoSwipePoint> trace) {
final count = trace.length;
final origin = trace.first.time.inMicroseconds;
final times = Float64List(count);
final xs = Float64List(count);
final ys = Float64List(count);
for (var i = 0; i < count; i++) {
times[i] = (trace[i].time.inMicroseconds - origin) / 1000;
xs[i] = trace[i].x;
ys[i] = trace[i].y;
}
var stageX = xs;
var stageY = ys;
final duration = times[count - 1];
if (duration > 1e-3) {
final samples = math.max(2, _roundHalfEven(duration / (1000 / 60)) + 1);
final grid = _linspace(0, duration, samples);
stageX = _interp(grid, times, xs);
stageY = _interp(grid, times, ys);
}
final positions = Float64List(stageX.length);
for (var i = 0; i < positions.length; i++) {
positions[i] = i.toDouble();
}
final index = _linspace(
0,
(stageX.length - 1).toDouble(),
futoSwipeTraceSamples,
);
final x = _interp(index, positions, stageX);
final y = _interp(index, positions, stageY);
return Float32List(futoSwipeTraceSamples * 2)
..setAll(0, x)
..setAll(futoSwipeTraceSamples, y);
}