@webaudiomodules/sdk
Version:
WebAudioModules Plugin SDK
334 lines (287 loc) • 12.7 kB
text/typescript
/* eslint-disable no-underscore-dangle */
/* eslint-disable no-plusplus */
import expect from './jestUtilities';
import { diffArray } from './testUtilities';
import getWamParameterInfo from '../src/WamParameterInfo.js';
import getWamParameterInterpolator from '../src/WamParameterInterpolator.js';
const samplesPerRenderQuantum = 128;
const WamParameterInfo = getWamParameterInfo();
const WamParameterInterpolator = getWamParameterInterpolator();
describe('WamParameterInterpolator Suite', () => {
// TODO setup should be in a 'beforeEach' but TS can't resolve scope...
let e = 0.0;
const samplesPerInterpolation = 32; // Note that some tests will fail if this is made very large
const startValue = -10;
const endValue = 10;
const infoA = new WamParameterInfo('A', { defaultValue: 0.5, minValue: startValue, maxValue: endValue });
const infoB = new WamParameterInfo('B', { defaultValue: 3.0, minValue: startValue, maxValue: endValue });
const testA = new WamParameterInterpolator(infoA, samplesPerInterpolation);
const testB = new WamParameterInterpolator(infoB, samplesPerInterpolation);
const initialKey = [samplesPerInterpolation, e].join('_');
let startIndex = 0;
let endIndex = 0;
it('Should manage lifecycles of static lookup tables', () => {
/* eslint-disable-next-line */
const tables = WamParameterInterpolator['_tables'];
/* eslint-disable-next-line */
const tableReferences = WamParameterInterpolator['_tableReferences'];
// Initial key and corresponding references should be present
expect(tables[initialKey]).toBeDefined();
// 2 references -- testA and testB share the table
expect(tableReferences[initialKey].length).toEqual(2);
e = 0.5;
testA.setSkew(e);
const additionalKey = [samplesPerInterpolation, e].join('_');
// Initial key and corresponding references should still be present
expect(tables[initialKey]).toBeDefined();
// 1 reference, only testB uses this table
expect(tableReferences[initialKey].length).toEqual(1);
// Expected key and corresponding references should be present
expect(tables[additionalKey]).toBeDefined();
// 1 reference, only testA uses this table
expect(tableReferences[additionalKey].length).toEqual(1);
testA.setSkew(0);
// Expected key and corresponding reference should no longer be present
expect(tables[additionalKey]).toBeUndefined();
expect(tableReferences[additionalKey]).toBeUndefined();
// Initial key and corresponding references should still be present
expect(tables[initialKey]).toBeDefined();
// 2 references, testA and testB share the same table again
expect(tableReferences[initialKey].length).toEqual(2);
// Tables and references should be cleaned up after destroying instances
testA.destroy();
// 1 reference, testB still uses this table
expect(tableReferences[initialKey].length).toEqual(1);
testB.destroy();
// 0 tables, 0 references, all instances destroyed
expect(tables[initialKey]).toBeUndefined();
expect(tableReferences[initialKey]).toBeUndefined();
});
it('Should be filled after call to setStartValue', () => {
const expectedValue = 0;
testA.setStartValue(expectedValue);
expect(testA.values).toAllEqual(expectedValue);
expect(testA.is(expectedValue)).toEqual(true);
});
it('Should only be done when filled with end value', () => {
testA.setStartValue(startValue);
expect(testA.is(startValue)).toEqual(true);
testA.setEndValue(endValue);
// No processing yet
expect(testA.done).toEqual(false);
expect(testA.is(startValue)).toEqual(false);
expect(testA.is(endValue)).toEqual(false);
startIndex = 0;
endIndex = Math.round(samplesPerInterpolation / 3);
testA.process(startIndex, endIndex);
// Partial slice (less than samplesPerInterpolation)
expect(testA.done).toEqual(false);
expect(testA.is(endValue)).toEqual(false);
startIndex = endIndex;
endIndex += samplesPerInterpolation - endIndex;
testA.process(startIndex, endIndex);
// Should have finished interpolating, but not yet filled
expect(testA.done).toEqual(false);
expect(testA.is(endValue)).toEqual(false);
startIndex = samplesPerInterpolation;
endIndex = samplesPerRenderQuantum;
testA.process(startIndex, endIndex);
const rampedSlice = testA.values.slice(0, samplesPerInterpolation);
const filledSlice = testA.values.slice(samplesPerInterpolation + 1, samplesPerRenderQuantum);
// Verify values
expect(rampedSlice).toAllIncrease();
expect(filledSlice).toAllEqual(endValue);
expect(testA.done).toEqual(false);
expect(testA.is(endValue)).toEqual(false);
startIndex = 0;
testA.process(startIndex, endIndex);
// Should have finishing filling values
expect(testA.done).toEqual(true);
expect(testA.values).toAllEqual(endValue);
expect(testA.is(endValue)).toEqual(true);
});
it('Should continue from current value when setting end value during interpolation', () => {
testA.setStartValue(startValue);
expect(testA.is(startValue)).toEqual(true);
testA.setEndValue(endValue);
expect(testA.is(startValue)).toEqual(false);
expect(testA.is(endValue)).toEqual(false);
const partialInterpolation = Math.round(samplesPerInterpolation / 3);
startIndex = 0;
endIndex = partialInterpolation;
testA.process(startIndex, endIndex);
// Partial slice (less than samplesPerInterpolation)
testA.setEndValue(startValue);
startIndex = endIndex;
endIndex += samplesPerInterpolation - endIndex;
testA.process(startIndex, endIndex);
// Should not have finished interpolating
expect(testA.done).toEqual(false);
expect(testA.is(endValue)).toEqual(false);
expect(testA.is(startValue)).toEqual(false);
startIndex = endIndex;
endIndex = samplesPerRenderQuantum;
testA.process(startIndex, endIndex);
// Should have finished interpolating but not filled
expect(testA.done).toEqual(false);
expect(testA.is(endValue)).toEqual(false);
expect(testA.is(startValue)).toEqual(false);
startIndex = 0;
endIndex = partialInterpolation;
const increasingSlice = testA.values.slice(startIndex, endIndex);
startIndex = endIndex;
endIndex = startIndex + samplesPerInterpolation;
const decreasingSlice = testA.values.slice(startIndex, endIndex);
startIndex = endIndex;
endIndex = samplesPerRenderQuantum;
const filledSlice = testA.values.slice(startIndex, endIndex);
// Verify values
expect(increasingSlice).toAllIncrease();
expect(decreasingSlice).toAllDecrease();
expect(filledSlice).toAllEqual(startValue);
expect(testA.done).toEqual(false);
expect(testA.is(endValue)).toEqual(false);
expect(testA.is(startValue)).toEqual(false);
startIndex = 0;
testA.process(startIndex, endIndex);
// Should have finishing filling values
expect(testA.done).toEqual(true);
expect(testA.values).toAllEqual(startValue);
expect(testA.is(endValue)).toEqual(false);
expect(testA.is(startValue)).toEqual(true);
});
it('Should not interpolate discrete parameters', () => {
const infoC = new WamParameterInfo('C', {
defaultValue: startValue,
minValue: startValue,
maxValue: endValue,
discreteStep: 1,
});
const testC = new WamParameterInterpolator(infoC, samplesPerInterpolation);
startIndex = 0;
endIndex = samplesPerInterpolation;
testC.process(startIndex, endIndex);
expect(testC.values.slice(startIndex, endIndex)).toAllEqual(startValue);
startIndex = endIndex;
endIndex += samplesPerInterpolation;
testC.setEndValue(endValue);
testC.process(startIndex, endIndex);
expect(testC.values.slice(startIndex, endIndex)).toAllEqual(endValue);
startIndex = endIndex;
endIndex = samplesPerRenderQuantum;
testC.process(startIndex, endIndex);
expect(testC.values.slice(startIndex, endIndex)).toAllEqual(endValue);
startIndex = 0;
testC.process(startIndex, endIndex);
expect(testC.values.slice(startIndex, endIndex)).toAllEqual(endValue);
});
it('Should work properly when interpolation period is greater than that of render quantum', () => {
const longerInterpolationPeriod = Math.round(samplesPerRenderQuantum * Math.PI);
const infoD = new WamParameterInfo('D', { defaultValue: startValue, minValue: startValue, maxValue: endValue });
const testD = new WamParameterInterpolator(infoD, longerInterpolationPeriod);
testD.setEndValue(endValue);
startIndex = 0;
endIndex = samplesPerRenderQuantum;
const remainder = longerInterpolationPeriod % samplesPerRenderQuantum;
let fullRenders = Math.floor(longerInterpolationPeriod / samplesPerRenderQuantum);
while (fullRenders--) {
testD.process(startIndex, endIndex);
expect(testD.values).toAllIncrease();
}
expect(testD.done).toBe(false);
expect(testD.is(endValue)).toEqual(false);
testD.process(0, samplesPerRenderQuantum);
expect(testD.done).toBe(false);
expect(testD.is(endValue)).toEqual(false);
expect(testD.values.slice(0, remainder)).toAllIncrease();
expect(testD.values.slice(remainder, samplesPerRenderQuantum)).toAllEqual(endValue);
testD.process(0, remainder);
expect(testD.done).toBe(true);
expect(testD.values).toAllEqual(endValue);
expect(testD.is(endValue)).toEqual(true);
});
it('Should throw if skew is set out of range', () => {
expect(() => new WamParameterInterpolator(infoA, 0, 1.001)).toThrow();
expect(() => new WamParameterInterpolator(infoA, 0, -1.001)).toThrow();
expect(() => new WamParameterInterpolator(infoA, 0, 1.0)).not.toThrow();
expect(() => new WamParameterInterpolator(infoA, 0, -1.0)).not.toThrow();
expect(() => testA.setSkew(1.001)).toThrow();
expect(() => testA.setSkew(-1.001)).toThrow();
expect(() => testA.setSkew(1.0)).not.toThrow();
expect(() => testA.setSkew(-1.0)).not.toThrow();
expect(() => testA.setSkew(0.0)).not.toThrow();
});
it('Should interpolate linearly when skew is zero', () => {
startIndex = 0;
endIndex = samplesPerInterpolation;
testA.setStartValue(startValue);
testA.setEndValue(endValue);
testA.process(startIndex, endIndex);
let diffs = diffArray(testA.values.slice(startIndex, endIndex));
let expectedDelta = (endValue - startValue) / samplesPerInterpolation;
expect(diffs).toAllEqual(expectedDelta);
startIndex = endIndex;
endIndex += samplesPerInterpolation;
testA.process(startIndex, endIndex);
expect(testA.values.slice(startIndex, endIndex)).toAllEqual(endValue);
testA.setEndValue(startValue);
startIndex = endIndex;
endIndex += samplesPerInterpolation;
testA.process(startIndex, endIndex);
diffs = diffArray(testA.values.slice(startIndex, endIndex));
expectedDelta *= -1;
expect(diffs).toAllEqual(expectedDelta);
startIndex = endIndex;
endIndex += samplesPerInterpolation;
testA.process(startIndex, endIndex);
expect(testA.values.slice(startIndex, endIndex)).toAllEqual(startValue);
});
it('Should interpolate nonlinearly (convex) when skew is greater than zero', () => {
startIndex = 0;
endIndex = samplesPerInterpolation;
testA.setSkew(0.667);
testA.setStartValue(startValue);
testA.setEndValue(endValue);
testA.process(startIndex, endIndex);
let diffs = diffArray(testA.values.slice(startIndex, endIndex));
expect(diffs).toAllDecrease();
startIndex = endIndex;
endIndex += samplesPerInterpolation;
testA.process(startIndex, endIndex);
expect(testA.values.slice(startIndex, endIndex)).toAllEqual(endValue);
testA.setEndValue(startValue);
startIndex = endIndex;
endIndex += samplesPerInterpolation;
testA.process(startIndex, endIndex);
diffs = diffArray(testA.values.slice(startIndex, endIndex));
expect(diffs).toAllDecrease();
startIndex = endIndex;
endIndex += samplesPerInterpolation;
testA.process(startIndex, endIndex);
expect(testA.values.slice(startIndex, endIndex)).toAllEqual(startValue);
});
it('Should interpolate nonlinearly (concave) when skew is less than zero', () => {
startIndex = 0;
endIndex = samplesPerInterpolation;
testA.setSkew(-0.667);
testA.setStartValue(startValue);
testA.setEndValue(endValue);
testA.process(startIndex, endIndex);
let diffs = diffArray(testA.values.slice(startIndex, endIndex));
expect(diffs).toAllIncrease();
startIndex = endIndex;
endIndex += samplesPerInterpolation;
testA.process(startIndex, endIndex);
expect(testA.values.slice(startIndex, endIndex)).toAllEqual(endValue);
testA.setEndValue(startValue);
startIndex = endIndex;
endIndex += samplesPerInterpolation;
testA.process(startIndex, endIndex);
diffs = diffArray(testA.values.slice(startIndex, endIndex));
expect(diffs).toAllIncrease();
startIndex = endIndex;
endIndex += samplesPerInterpolation;
testA.process(startIndex, endIndex);
expect(testA.values.slice(startIndex, endIndex)).toAllEqual(startValue);
});
});