diff --git a/scripts/test_goniometer.mjs b/scripts/test_goniometer.mjs
index bb824d3..4b61f3a 100644
--- a/scripts/test_goniometer.mjs
+++ b/scripts/test_goniometer.mjs
@@ -8,6 +8,10 @@ assert.ok(functionSource, 'persistence resolver must remain testable');
const context = vm.createContext({ Number, String, Math });
vm.runInContext(functionSource.replace('export function', 'function'), context);
+const markerFunctionSource = source.match(/export function resolveCorrelationNegativeMarker[\s\S]*?\n\}/)?.[0];
+assert.ok(markerFunctionSource, 'correlation marker resolver must remain testable');
+vm.runInContext(`const CORR_HOLD_MS = 4000;\n${markerFunctionSource.replace('export function', 'function')}`, context);
+
assert.equal(context.resolveGoniometerPersistenceMs({ GONIO_PERSISTENCE_MODE: 'fast' }), 50);
assert.equal(context.resolveGoniometerPersistenceMs({ GONIO_PERSISTENCE_MODE: 'medium' }), 150);
assert.equal(context.resolveGoniometerPersistenceMs({ GONIO_PERSISTENCE_MODE: 'slow' }), 300);
@@ -28,4 +32,29 @@ assert.match(source, /pool\.pop\(\)/, 'trail buffers must be reused');
assert.doesNotMatch(source, /utils\.correlation\(/,
'browser must not recalculate or smooth backend correlation');
+const markerState = {
+ corrNegativeMarkerMode: 'memory',
+ corrHoldPeak: 0,
+ corrHoldUntil: 0,
+ corrResetToken: 0,
+};
+assert.equal(context.resolveCorrelationNegativeMarker(
+ markerState, { CORR_NEGATIVE_MARKER_MODE: 'memory' }, -0.2, -0.8, 1000
+), -0.8, 'memory mode must display the continuous DSP negative peak');
+assert.equal(context.resolveCorrelationNegativeMarker(
+ markerState, { CORR_NEGATIVE_MARKER_MODE: 'off' }, -0.9, -0.9, 1100
+), 0, 'off mode must hide the negative marker');
+assert.equal(context.resolveCorrelationNegativeMarker(
+ markerState, { CORR_NEGATIVE_MARKER_MODE: 'hold' }, -0.4, -0.9, 2000
+), -0.4, 'hold mode must start from the current correlation');
+assert.equal(context.resolveCorrelationNegativeMarker(
+ markerState, { CORR_NEGATIVE_MARKER_MODE: 'hold' }, -0.2, -0.9, 5000
+), -0.4, 'hold mode must retain its negative peak for four seconds');
+assert.equal(context.resolveCorrelationNegativeMarker(
+ markerState, { CORR_NEGATIVE_MARKER_MODE: 'hold' }, -0.2, -0.9, 6000
+), -0.2, 'hold mode must release to the current value after four seconds');
+assert.equal(context.resolveCorrelationNegativeMarker(
+ markerState, { CORR_NEGATIVE_MARKER_MODE: 'hold', CORR_RESET_TOKEN: 1 }, 0.2, -0.9, 6100
+), 0, 'reset must clear the four-second hold as well');
+
console.log('goniometer regression tests passed');
diff --git a/www/core/config.js b/www/core/config.js
index 4a5ef30..76efce9 100644
--- a/www/core/config.js
+++ b/www/core/config.js
@@ -190,6 +190,7 @@ const CONFIG = {
CORR_ZERO_ON_SILENCE: false,
CORR_SILENCE_THRESHOLD_RMS_DBFS: -75,
CORR_RESPONSE_S: 1.0,
+ CORR_NEGATIVE_MARKER_MODE: 'memory',
CORR_RESET_TOKEN: 0,
XY_POINTS: 1024,
XY_STYLE: 'lines',
@@ -958,6 +959,10 @@ function loadConfig(opts = {}) {
CONFIG.GONIO_MANUAL_GAIN_DB = Math.max(-35, Math.min(35, Math.round(fallback / 5) * 5));
}
CONFIG.CORR_RESPONSE_S = Number(CONFIG.CORR_RESPONSE_S) >= 1.75 ? 2.5 : 1.0;
+ const corrNegativeMarkerMode = String(CONFIG.CORR_NEGATIVE_MARKER_MODE || 'memory').toLowerCase();
+ CONFIG.CORR_NEGATIVE_MARKER_MODE = ['memory', 'hold', 'off'].includes(corrNegativeMarkerMode)
+ ? corrNegativeMarkerMode
+ : 'memory';
CONFIG.CORR_RESET_TOKEN = Math.max(0, Math.floor(Number(CONFIG.CORR_RESET_TOKEN) || 0));
const persistenceMode = String(CONFIG.GONIO_PERSISTENCE_MODE || 'fast').toLowerCase();
CONFIG.GONIO_PERSISTENCE_MODE = ['fast', 'medium', 'slow', 'custom'].includes(persistenceMode)
diff --git a/www/index.html b/www/index.html
index e0730e7..8a0830f 100644
--- a/www/index.html
+++ b/www/index.html
@@ -534,9 +534,17 @@
Kontinuierliche DSP-Integration; unabhängig von Bildrate und ALSA-Periode.
+
+
+ Memory bleibt bis zum Zurücksetzen stehen; Hold speichert die negativste Anzeige vier Sekunden.
+
- Setzt den Negative-Peak-Memory auf 0 zurück; der rote Marker verschwindet.
+ Setzt den gespeicherten Negativmarker zurück. Im Modus Off bleibt der Marker ausgeblendet.