feat: added pitch bend automation (linear interpolation)
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
bakeMidiAutomationPointsInWindow,
|
||||
collectRegionMidiAutomationPoints,
|
||||
normalizeMidiAutomationPoints,
|
||||
resolveMidiAutomationValueAtBeat,
|
||||
type MidiAutomationPoint,
|
||||
} from './midiAutomationUtil';
|
||||
|
||||
const defaultOptions = {
|
||||
maxIntervalMs: 10,
|
||||
bpm: 120,
|
||||
defaultValue: 8192,
|
||||
};
|
||||
|
||||
describe('midiAutomationUtil', () => {
|
||||
it('normalizes points and keeps the last duplicate beat', () => {
|
||||
const points: MidiAutomationPoint[] = [
|
||||
{ beat: 2, value: 1000 },
|
||||
{ beat: 1, value: 2000 },
|
||||
{ beat: 2, value: 3000 },
|
||||
];
|
||||
|
||||
expect(normalizeMidiAutomationPoints(points)).toEqual([
|
||||
{ beat: 1, value: 2000 },
|
||||
{ beat: 2, value: 3000 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('holds the default value before the first point', () => {
|
||||
const points = [{ beat: 4, value: 0 }];
|
||||
|
||||
expect(resolveMidiAutomationValueAtBeat(points, 2, 8192)).toBe(8192);
|
||||
});
|
||||
|
||||
it('interpolates linearly between two points', () => {
|
||||
const points = [
|
||||
{ beat: 0, value: 8192 },
|
||||
{ beat: 4, value: 0 },
|
||||
];
|
||||
|
||||
expect(resolveMidiAutomationValueAtBeat(points, 2, 8192)).toBe(4096);
|
||||
});
|
||||
|
||||
it('holds the last value after the final point', () => {
|
||||
const points = [{ beat: 1, value: 2048 }];
|
||||
|
||||
expect(resolveMidiAutomationValueAtBeat(points, 8, 8192)).toBe(2048);
|
||||
});
|
||||
|
||||
it('collects region-local points into absolute beats', () => {
|
||||
const collected = collectRegionMidiAutomationPoints([
|
||||
{ startBeat: 4, points: [{ beat: 0.5, value: 7000 }] },
|
||||
{ startBeat: 1, points: [{ beat: 0.25, value: 6000 }] },
|
||||
]);
|
||||
|
||||
expect(collected).toEqual([
|
||||
{ beat: 1.25, value: 6000 },
|
||||
{ beat: 4.5, value: 7000 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('bakes dense points according to the requested ms interval', () => {
|
||||
const points = [
|
||||
{ beat: 0, value: 8192 },
|
||||
{ beat: 1, value: 0 },
|
||||
];
|
||||
|
||||
expect(bakeMidiAutomationPointsInWindow(points, 0, 1, { ...defaultOptions, maxIntervalMs: 20 })).toHaveLength(25);
|
||||
expect(bakeMidiAutomationPointsInWindow(points, 0, 1, { ...defaultOptions, maxIntervalMs: 10 })).toHaveLength(50);
|
||||
expect(bakeMidiAutomationPointsInWindow(points, 0, 1, { ...defaultOptions, maxIntervalMs: 5 })).toHaveLength(100);
|
||||
});
|
||||
|
||||
it('treats flat segments as holds without adding interior baked points', () => {
|
||||
const points = [
|
||||
{ beat: 0, value: 4096 },
|
||||
{ beat: 4, value: 4096 },
|
||||
];
|
||||
|
||||
expect(bakeMidiAutomationPointsInWindow(points, 0, 4, { ...defaultOptions, maxIntervalMs: 10 })).toEqual([
|
||||
{ beat: 0, value: 4096 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('collapses consecutive baked points with the same value', () => {
|
||||
const points = [
|
||||
{ beat: 0.5, value: 8192 },
|
||||
{ beat: 1, value: 4096 },
|
||||
{ beat: 2, value: 4096 },
|
||||
{ beat: 3, value: 0 },
|
||||
];
|
||||
|
||||
expect(bakeMidiAutomationPointsInWindow(points, 0, 4, { ...defaultOptions, maxIntervalMs: 500 })).toEqual([
|
||||
{ beat: 0, value: 8192 },
|
||||
{ beat: 1, value: 4096 },
|
||||
{ beat: 3, value: 0 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps later changing segments after skipping a flat segment', () => {
|
||||
const points = [
|
||||
{ beat: 0, value: 4096 },
|
||||
{ beat: 2, value: 4096 },
|
||||
{ beat: 4, value: 0 },
|
||||
];
|
||||
|
||||
const baked = bakeMidiAutomationPointsInWindow(points, 0, 4, { ...defaultOptions, maxIntervalMs: 500 });
|
||||
|
||||
expect(baked).toEqual([
|
||||
{ beat: 0, value: 4096 },
|
||||
{ beat: 3, value: 2048 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('adds a window anchor and preserves the correct loop boundary value', () => {
|
||||
const points = [
|
||||
{ beat: 2, value: 0 },
|
||||
{ beat: 6, value: 8192 },
|
||||
];
|
||||
|
||||
const baked = bakeMidiAutomationPointsInWindow(points, 4, 8, { ...defaultOptions, maxIntervalMs: 500 });
|
||||
|
||||
expect(baked[0]).toEqual({ beat: 4, value: 4096 });
|
||||
expect(baked.some(point => point.beat === 5 && point.value === 6144)).toBe(true);
|
||||
expect(baked.some(point => point.beat === 6 && point.value === 8192)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user