Improve phase trail and correlation controls
This commit is contained in:
@@ -16,4 +16,47 @@ assert.ok(Math.abs(twoFrames - context.smoothingAlpha(1 / 30, 0.2)) < 1e-12,
|
||||
assert.doesNotMatch(source, /auto\.gain \* PHASE_AGC_BASE_GAIN/,
|
||||
'AGC target gain must not be multiplied a second time');
|
||||
|
||||
const maxAngleStep = Number(
|
||||
source.match(/PHASE_TRAIL_MAX_ANGLE_STEP_RAD\s*=\s*\(([\d.]+)\s*\*\s*Math\.PI\)\s*\/\s*180/)?.[1],
|
||||
) * Math.PI / 180;
|
||||
assert.ok(Number.isFinite(maxAngleStep) && maxAngleStep <= (2 * Math.PI) / 180,
|
||||
'phase trail interpolation must limit angular steps to at most two degrees');
|
||||
|
||||
const interpolationSource = source.match(/function appendPhaseTrailSegment[\s\S]*?\n\}/)?.[0];
|
||||
assert.ok(interpolationSource, 'phase trail must interpolate polar segments');
|
||||
|
||||
const trailContext = vm.createContext({ Number, Math });
|
||||
const trailHelpers = [
|
||||
source.match(/const PHASE_TRAIL_MAX_ANGLE_STEP_RAD\s*=.*?;/)?.[0],
|
||||
source.match(/function radToDeg[\s\S]*?\n\}/)?.[0],
|
||||
source.match(/function trailColorForAngle[\s\S]*?\n\}/)?.[0],
|
||||
source.match(/function wrapAngle[\s\S]*?\n\}/)?.[0],
|
||||
source.match(/function lerp[\s\S]*?\n\}/)?.[0],
|
||||
source.match(/function createPhaseTrailPoint[\s\S]*?\n\}/)?.[0],
|
||||
interpolationSource,
|
||||
];
|
||||
assert.ok(trailHelpers.every(Boolean), 'phase trail interpolation helpers must remain testable');
|
||||
vm.runInContext(trailHelpers.join('\n'), trailContext);
|
||||
|
||||
const wheel = { cx: 0, cy: 0, radius: 100 };
|
||||
const trail = [trailContext.createPhaseTrailPoint(wheel, -Math.PI / 2, 1, 0)];
|
||||
trailContext.appendPhaseTrailSegment(trail, wheel, trail[0], Math.PI / 2, 1, 100);
|
||||
assert.equal(trail.length, 91,
|
||||
'a 180-degree phase change must be split into 90 two-degree segments');
|
||||
for (let index = 1; index < trail.length; index++) {
|
||||
const previousAngle = Math.atan2(trail[index - 1].y, trail[index - 1].x);
|
||||
const currentAngle = Math.atan2(trail[index].y, trail[index].x);
|
||||
const angularStep = Math.abs(Math.atan2(
|
||||
Math.sin(currentAngle - previousAngle),
|
||||
Math.cos(currentAngle - previousAngle),
|
||||
));
|
||||
assert.ok(angularStep <= maxAngleStep + 1e-12,
|
||||
'interpolated trail segments must respect the maximum angular step');
|
||||
assert.ok(Math.abs(Math.hypot(trail[index].x, trail[index].y) - wheel.radius) < 1e-9,
|
||||
'constant-radius phase movement must remain on a circular arc');
|
||||
}
|
||||
|
||||
assert.match(source, /PHASE_TRAIL_MAX_POINTS/,
|
||||
'interpolated phase trail history must remain bounded');
|
||||
|
||||
console.log('phase wheel regression tests passed');
|
||||
|
||||
@@ -5,10 +5,26 @@ import { getRtwCenters } from '../www/core/rtw_centers.js';
|
||||
import { buildRtwTickPositions, selectLocalRtwPacket } from '../www/views/realtime.js';
|
||||
|
||||
const source = fs.readFileSync(new URL('../www/core/audio.js', import.meta.url), 'utf8');
|
||||
const configSource = fs.readFileSync(new URL('../www/core/config.js', import.meta.url), 'utf8');
|
||||
const correlationNormalizerSource = configSource.match(
|
||||
/export function normalizeCorrelationResponseSeconds[\s\S]*?\n\}/,
|
||||
)?.[0];
|
||||
assert.ok(correlationNormalizerSource, 'correlation response normalization must remain testable');
|
||||
const correlationContext = vm.createContext({ Number });
|
||||
vm.runInContext(
|
||||
correlationNormalizerSource.replace('export function', 'function'),
|
||||
correlationContext,
|
||||
);
|
||||
assert.equal(correlationContext.normalizeCorrelationResponseSeconds(0.5), 0.5);
|
||||
assert.equal(correlationContext.normalizeCorrelationResponseSeconds(1.0), 1.0);
|
||||
assert.equal(correlationContext.normalizeCorrelationResponseSeconds(2.5), 2.5);
|
||||
assert.equal(correlationContext.normalizeCorrelationResponseSeconds(Number.NaN), 1.0);
|
||||
|
||||
const functionSource = source.match(/export function buildRtaRuntimeConfig[\s\S]*?\n\}/)?.[0];
|
||||
assert.ok(functionSource, 'buildRtaRuntimeConfig must remain testable');
|
||||
const context = vm.createContext({
|
||||
Number,
|
||||
normalizeCorrelationResponseSeconds: correlationContext.normalizeCorrelationResponseSeconds,
|
||||
getRtwCenters(mode) {
|
||||
if (mode === '1_12') return new Array(121).fill(0);
|
||||
if (mode === '1_6') return new Array(61).fill(0);
|
||||
@@ -29,6 +45,7 @@ const forced = context.buildRtaRuntimeConfig({
|
||||
RTA_PEAK_DECAY_DB_PER_S: 20,
|
||||
RTA_PEAK_RESET_TOKEN: 9,
|
||||
CORR_RESPONSE_S: 2.5,
|
||||
CORR_SILENCE_THRESHOLD_RMS_DBFS: -50,
|
||||
CORR_RESET_TOKEN: 7,
|
||||
XY_POINTS: 256,
|
||||
});
|
||||
@@ -44,9 +61,13 @@ assert.equal(forced.rtaPeakResetToken, 9);
|
||||
assert.equal(forced.tauFast, 0.125);
|
||||
assert.equal(forced.rtwCenters.length, 121);
|
||||
assert.equal(forced.correlationResponseS, 2.5);
|
||||
assert.equal(forced.correlationSilenceThresholdRmsDbfs, -50);
|
||||
assert.equal(forced.correlationResetToken, 7);
|
||||
assert.equal(forced.xyPoints, 256);
|
||||
|
||||
const veryFastCorrelation = context.buildRtaRuntimeConfig({ CORR_RESPONSE_S: 0.5 });
|
||||
assert.equal(veryFastCorrelation.correlationResponseS, 0.5);
|
||||
|
||||
const extension = context.buildRtaRuntimeConfig({
|
||||
RTA_BAR_LAYOUT: 'iec',
|
||||
RTA_ENGINE: 'fft',
|
||||
@@ -59,7 +80,6 @@ assert.equal(extension.bpo, '1_12');
|
||||
assert.equal(extension.freqRange, 'lf');
|
||||
assert.equal(extension.order, 8);
|
||||
|
||||
const configSource = fs.readFileSync(new URL('../www/core/config.js', import.meta.url), 'utf8');
|
||||
const selectionSource = configSource.match(/function applyRtaBpoSelection[\s\S]*?\n\}/)?.[0];
|
||||
assert.ok(selectionSource, 'RTA BPO selection policy must remain testable');
|
||||
const selectionContext = vm.createContext({
|
||||
|
||||
Reference in New Issue
Block a user