Add RTA hold and fall mode
This commit is contained in:
@@ -72,7 +72,7 @@ Diese vorhandenen Funktionen sind nicht automatisch messtechnisch korrekt. Die f
|
|||||||
|
|
||||||
- [ ] **7. Peak Hold und Bandspeicher wie beim PortaMonitor ergänzen**
|
- [ ] **7. Peak Hold und Bandspeicher wie beim PortaMonitor ergänzen**
|
||||||
- **Soll:** Peak Hold 2,5 s, 4 s oder manuell; Speicher für acht Bänder plus Hold.
|
- **Soll:** Peak Hold 2,5 s, 4 s oder manuell; Speicher für acht Bänder plus Hold.
|
||||||
- **Ist:** 2,5 s, 4 s und manueller Hold werden samplezeitbasiert im Backend geführt. Nach Zeitablauf springt der Hold eindeutig auf den aktuellen Detektorwert; ein kontinuierlicher Rücklauf findet nicht mehr statt. Der Reset erreicht über ein Token sowohl Backend als auch Anzeige. FFT und IIR verwenden dieselbe Hold-Semantik, ohne eine zweite Browser-Holdstufe.
|
- **Ist:** 2,5 s, 4 s und manueller Hold werden samplezeitbasiert im Backend geführt. Der Modus `Hold` springt nach Ablauf eindeutig auf den aktuellen Detektorwert. Der zusätzliche Standardmodus `Hold + Fall` hält zunächst genauso lange und läuft danach mit einstellbaren 1 bis 60 dB/s zurück; Standard sind 20 dB/s. Der Reset erreicht über ein Token sowohl Backend als auch Anzeige. FFT und IIR verwenden dieselbe Hold-Semantik, ohne eine zweite Browser-Holdstufe.
|
||||||
- **Noch offen:** Der achtbandige RTW-Speicher ist noch nicht ergänzt.
|
- **Noch offen:** Der achtbandige RTW-Speicher ist noch nicht ergänzt.
|
||||||
- **Aufgabe:** Acht auswählbare Speicherbänder implementieren und Aktuell-, Hold- und Speicherwert visuell eindeutig trennen.
|
- **Aufgabe:** Acht auswählbare Speicherbänder implementieren und Aktuell-, Hold- und Speicherwert visuell eindeutig trennen.
|
||||||
- **Abnahme:** Hold-Zeiten und Reset-Verhalten stimmen zeitlich und visuell mit der Referenz überein.
|
- **Abnahme:** Hold-Zeiten und Reset-Verhalten stimmen zeitlich und visuell mit der Referenz überein.
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ const forced = context.buildRtaRuntimeConfig({
|
|||||||
RTA_FREQ_RANGE: 'lf',
|
RTA_FREQ_RANGE: 'lf',
|
||||||
RTA_IIR_ORDER: 2,
|
RTA_IIR_ORDER: 2,
|
||||||
RTA_DETECTOR: 'peak',
|
RTA_DETECTOR: 'peak',
|
||||||
RTA_PEAK_HOLD_MODE: 'manual',
|
RTA_PEAK_HOLD_MODE: 'fall',
|
||||||
RTA_PEAK_HOLD_SEC: 4,
|
RTA_PEAK_HOLD_SEC: 4,
|
||||||
|
RTA_PEAK_DECAY_DB_PER_S: 20,
|
||||||
RTA_PEAK_RESET_TOKEN: 9,
|
RTA_PEAK_RESET_TOKEN: 9,
|
||||||
CORR_RESPONSE_S: 2.5,
|
CORR_RESPONSE_S: 2.5,
|
||||||
CORR_RESET_TOKEN: 7,
|
CORR_RESET_TOKEN: 7,
|
||||||
@@ -34,8 +35,9 @@ assert.equal(forced.bpo, '1_12');
|
|||||||
assert.equal(forced.freqRange, 'norm');
|
assert.equal(forced.freqRange, 'norm');
|
||||||
assert.equal(forced.order, 6);
|
assert.equal(forced.order, 6);
|
||||||
assert.equal(forced.detector, 'peak');
|
assert.equal(forced.detector, 'peak');
|
||||||
assert.equal(forced.rtaPeakHoldMode, 'manual');
|
assert.equal(forced.rtaPeakHoldMode, 'fall');
|
||||||
assert.equal(forced.rtaPeakHoldSeconds, 4);
|
assert.equal(forced.rtaPeakHoldSeconds, 4);
|
||||||
|
assert.equal(forced.rtaPeakDecayDbPerSecond, 20);
|
||||||
assert.equal(forced.rtaPeakResetToken, 9);
|
assert.equal(forced.rtaPeakResetToken, 9);
|
||||||
assert.equal(forced.tauFast, 0.125);
|
assert.equal(forced.tauFast, 0.125);
|
||||||
assert.equal(forced.rtwCenters.length, 121);
|
assert.equal(forced.rtwCenters.length, 121);
|
||||||
|
|||||||
+28
-2
@@ -207,6 +207,7 @@ impl RtaPeakHold {
|
|||||||
sample_rate: u32,
|
sample_rate: u32,
|
||||||
config: &PhoenixRtaConfig,
|
config: &PhoenixRtaConfig,
|
||||||
) {
|
) {
|
||||||
|
let previous_clock = self.sample_clock;
|
||||||
self.sample_clock = self.sample_clock.saturating_add(advanced_samples as u64);
|
self.sample_clock = self.sample_clock.saturating_add(advanced_samples as u64);
|
||||||
let mode = normalize_rta_peak_hold_mode(&config.rta_peak_hold_mode);
|
let mode = normalize_rta_peak_hold_mode(&config.rta_peak_hold_mode);
|
||||||
let hold_samples = (f64::from(sample_rate.max(1))
|
let hold_samples = (f64::from(sample_rate.max(1))
|
||||||
@@ -242,7 +243,7 @@ impl RtaPeakHold {
|
|||||||
};
|
};
|
||||||
if incoming >= self.values[index] {
|
if incoming >= self.values[index] {
|
||||||
self.values[index] = incoming;
|
self.values[index] = incoming;
|
||||||
if mode == "auto" {
|
if mode == "auto" || mode == "fall" {
|
||||||
self.expires_at_sample[index] = self.sample_clock.saturating_add(hold_samples);
|
self.expires_at_sample[index] = self.sample_clock.saturating_add(hold_samples);
|
||||||
}
|
}
|
||||||
} else if mode == "auto" && self.sample_clock >= self.expires_at_sample[index] {
|
} else if mode == "auto" && self.sample_clock >= self.expires_at_sample[index] {
|
||||||
@@ -250,6 +251,14 @@ impl RtaPeakHold {
|
|||||||
// value. There is deliberately no second, continuous peak decay.
|
// value. There is deliberately no second, continuous peak decay.
|
||||||
self.values[index] = incoming;
|
self.values[index] = incoming;
|
||||||
self.expires_at_sample[index] = self.sample_clock.saturating_add(hold_samples);
|
self.expires_at_sample[index] = self.sample_clock.saturating_add(hold_samples);
|
||||||
|
} else if mode == "fall" && self.sample_clock >= self.expires_at_sample[index] {
|
||||||
|
let decay_start = previous_clock.max(self.expires_at_sample[index]);
|
||||||
|
let decay_samples = self.sample_clock.saturating_sub(decay_start);
|
||||||
|
let decay_db = config.rta_peak_decay_db_per_second * decay_samples as f32
|
||||||
|
/ sample_rate.max(1) as f32;
|
||||||
|
self.values[index] = (self.values[index] - decay_db)
|
||||||
|
.max(incoming)
|
||||||
|
.max(RTA_PEAK_FLOOR_DB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1944,8 +1953,10 @@ fn normalize_rta_integration(value: &str) -> &'static str {
|
|||||||
fn normalize_rta_peak_hold_mode(value: &str) -> &'static str {
|
fn normalize_rta_peak_hold_mode(value: &str) -> &'static str {
|
||||||
match value.trim().to_ascii_lowercase().as_str() {
|
match value.trim().to_ascii_lowercase().as_str() {
|
||||||
"off" => "off",
|
"off" => "off",
|
||||||
|
"fall" => "fall",
|
||||||
"manual" => "manual",
|
"manual" => "manual",
|
||||||
_ => "auto",
|
"auto" => "auto",
|
||||||
|
_ => "fall",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2317,6 +2328,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn rta_peak_hold_has_timed_manual_off_and_reset_semantics() {
|
fn rta_peak_hold_has_timed_manual_off_and_reset_semantics() {
|
||||||
let mut config = PhoenixRtaConfig::default();
|
let mut config = PhoenixRtaConfig::default();
|
||||||
|
config.rta_peak_hold_mode = "auto".to_string();
|
||||||
config.rta_peak_hold_seconds = 0.1;
|
config.rta_peak_hold_seconds = 0.1;
|
||||||
let mut hold = RtaPeakHold::new(1);
|
let mut hold = RtaPeakHold::new(1);
|
||||||
|
|
||||||
@@ -2329,6 +2341,20 @@ mod tests {
|
|||||||
"timed hold must release directly to current"
|
"timed hold must release directly to current"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
config.rta_peak_hold_mode = "fall".to_string();
|
||||||
|
config.rta_peak_decay_db_per_second = 20.0;
|
||||||
|
hold.update(&[-10.0], 1, 1_000, &config);
|
||||||
|
hold.update(&[-40.0], 100, 1_000, &config);
|
||||||
|
assert_eq!(
|
||||||
|
hold.values[0], -10.0,
|
||||||
|
"fall mode must first observe the hold time"
|
||||||
|
);
|
||||||
|
hold.update(&[-40.0], 500, 1_000, &config);
|
||||||
|
assert!(
|
||||||
|
(hold.values[0] - -20.0).abs() < 1.0e-6,
|
||||||
|
"fall must use the configured dB/s rate"
|
||||||
|
);
|
||||||
|
|
||||||
config.rta_peak_hold_mode = "manual".to_string();
|
config.rta_peak_hold_mode = "manual".to_string();
|
||||||
hold.update(&[-20.0], 1, 1_000, &config);
|
hold.update(&[-20.0], 1, 1_000, &config);
|
||||||
hold.update(&[-5.0], 1, 1_000, &config);
|
hold.update(&[-5.0], 1, 1_000, &config);
|
||||||
|
|||||||
+3
-1
@@ -70,6 +70,7 @@ pub struct PhoenixRtaConfig {
|
|||||||
pub layout: String,
|
pub layout: String,
|
||||||
pub rta_peak_hold_mode: String,
|
pub rta_peak_hold_mode: String,
|
||||||
pub rta_peak_hold_seconds: f32,
|
pub rta_peak_hold_seconds: f32,
|
||||||
|
pub rta_peak_decay_db_per_second: f32,
|
||||||
pub rta_peak_reset_token: u64,
|
pub rta_peak_reset_token: u64,
|
||||||
pub input_offset_db_l: f32,
|
pub input_offset_db_l: f32,
|
||||||
pub input_offset_db_r: f32,
|
pub input_offset_db_r: f32,
|
||||||
@@ -103,8 +104,9 @@ impl Default for PhoenixRtaConfig {
|
|||||||
tau_slow: 1.0,
|
tau_slow: 1.0,
|
||||||
integration: "fast".to_string(),
|
integration: "fast".to_string(),
|
||||||
layout: "rtw".to_string(),
|
layout: "rtw".to_string(),
|
||||||
rta_peak_hold_mode: "auto".to_string(),
|
rta_peak_hold_mode: "fall".to_string(),
|
||||||
rta_peak_hold_seconds: 2.5,
|
rta_peak_hold_seconds: 2.5,
|
||||||
|
rta_peak_decay_db_per_second: 20.0,
|
||||||
rta_peak_reset_token: 0,
|
rta_peak_reset_token: 0,
|
||||||
input_offset_db_l: -5.0,
|
input_offset_db_l: -5.0,
|
||||||
input_offset_db_r: -5.0,
|
input_offset_db_r: -5.0,
|
||||||
|
|||||||
+12
-3
@@ -414,8 +414,10 @@ fn normalize_rta_config(mut config: PhoenixRtaConfig) -> PhoenixRtaConfig {
|
|||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
"off" => "off".to_string(),
|
"off" => "off".to_string(),
|
||||||
|
"fall" => "fall".to_string(),
|
||||||
"manual" => "manual".to_string(),
|
"manual" => "manual".to_string(),
|
||||||
_ => "auto".to_string(),
|
"auto" => "auto".to_string(),
|
||||||
|
_ => "fall".to_string(),
|
||||||
};
|
};
|
||||||
let requested_hold = if config.rta_peak_hold_seconds.is_finite() {
|
let requested_hold = if config.rta_peak_hold_seconds.is_finite() {
|
||||||
config.rta_peak_hold_seconds
|
config.rta_peak_hold_seconds
|
||||||
@@ -431,6 +433,11 @@ fn normalize_rta_config(mut config: PhoenixRtaConfig) -> PhoenixRtaConfig {
|
|||||||
} else {
|
} else {
|
||||||
requested_hold.clamp(0.0, 30.0)
|
requested_hold.clamp(0.0, 30.0)
|
||||||
};
|
};
|
||||||
|
config.rta_peak_decay_db_per_second = if config.rta_peak_decay_db_per_second.is_finite() {
|
||||||
|
config.rta_peak_decay_db_per_second.clamp(1.0, 60.0)
|
||||||
|
} else {
|
||||||
|
20.0
|
||||||
|
};
|
||||||
let offset_l = if config.input_offset_db_l.is_finite() {
|
let offset_l = if config.input_offset_db_l.is_finite() {
|
||||||
config.input_offset_db_l
|
config.input_offset_db_l
|
||||||
} else {
|
} else {
|
||||||
@@ -792,16 +799,18 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rta_peak_hold_config_is_normalized_without_decay_parameter() {
|
fn rta_peak_hold_config_is_normalized() {
|
||||||
let rtw = normalize_rta_config(PhoenixRtaConfig {
|
let rtw = normalize_rta_config(PhoenixRtaConfig {
|
||||||
rta_peak_hold_mode: "MANUAL".to_string(),
|
rta_peak_hold_mode: "MANUAL".to_string(),
|
||||||
rta_peak_hold_seconds: 3.7,
|
rta_peak_hold_seconds: 3.7,
|
||||||
|
rta_peak_decay_db_per_second: 25.0,
|
||||||
rta_peak_reset_token: 12,
|
rta_peak_reset_token: 12,
|
||||||
layout: "rtw".to_string(),
|
layout: "rtw".to_string(),
|
||||||
..PhoenixRtaConfig::default()
|
..PhoenixRtaConfig::default()
|
||||||
});
|
});
|
||||||
assert_eq!(rtw.rta_peak_hold_mode, "manual");
|
assert_eq!(rtw.rta_peak_hold_mode, "manual");
|
||||||
assert_eq!(rtw.rta_peak_hold_seconds, 4.0);
|
assert_eq!(rtw.rta_peak_hold_seconds, 4.0);
|
||||||
|
assert_eq!(rtw.rta_peak_decay_db_per_second, 25.0);
|
||||||
assert_eq!(rtw.rta_peak_reset_token, 12);
|
assert_eq!(rtw.rta_peak_reset_token, 12);
|
||||||
|
|
||||||
let extension = normalize_rta_config(PhoenixRtaConfig {
|
let extension = normalize_rta_config(PhoenixRtaConfig {
|
||||||
@@ -810,7 +819,7 @@ mod tests {
|
|||||||
layout: "iec".to_string(),
|
layout: "iec".to_string(),
|
||||||
..PhoenixRtaConfig::default()
|
..PhoenixRtaConfig::default()
|
||||||
});
|
});
|
||||||
assert_eq!(extension.rta_peak_hold_mode, "auto");
|
assert_eq!(extension.rta_peak_hold_mode, "fall");
|
||||||
assert_eq!(extension.rta_peak_hold_seconds, 30.0);
|
assert_eq!(extension.rta_peak_hold_seconds, 30.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -916,10 +916,11 @@ export function buildRtaRuntimeConfig(CONFIG = {}) {
|
|||||||
tauSlow: CONFIG.RTA_IIR_TAU_SLOW || 1.0,
|
tauSlow: CONFIG.RTA_IIR_TAU_SLOW || 1.0,
|
||||||
integration: CONFIG.RTA_INTEGRATION || 'fast',
|
integration: CONFIG.RTA_INTEGRATION || 'fast',
|
||||||
layout,
|
layout,
|
||||||
rtaPeakHoldMode: ['off', 'manual'].includes(CONFIG.RTA_PEAK_HOLD_MODE)
|
rtaPeakHoldMode: ['off', 'auto', 'fall', 'manual'].includes(CONFIG.RTA_PEAK_HOLD_MODE)
|
||||||
? CONFIG.RTA_PEAK_HOLD_MODE
|
? CONFIG.RTA_PEAK_HOLD_MODE
|
||||||
: 'auto',
|
: 'fall',
|
||||||
rtaPeakHoldSeconds: Number.isFinite(CONFIG.RTA_PEAK_HOLD_SEC) ? CONFIG.RTA_PEAK_HOLD_SEC : 2.5,
|
rtaPeakHoldSeconds: Number.isFinite(CONFIG.RTA_PEAK_HOLD_SEC) ? CONFIG.RTA_PEAK_HOLD_SEC : 2.5,
|
||||||
|
rtaPeakDecayDbPerSecond: Number.isFinite(CONFIG.RTA_PEAK_DECAY_DB_PER_S) ? CONFIG.RTA_PEAK_DECAY_DB_PER_S : 20,
|
||||||
rtaPeakResetToken: Math.max(0, Math.floor(Number(CONFIG.RTA_PEAK_RESET_TOKEN) || 0)),
|
rtaPeakResetToken: Math.max(0, Math.floor(Number(CONFIG.RTA_PEAK_RESET_TOKEN) || 0)),
|
||||||
inputOffsetDbL: Number.isFinite(CONFIG.INPUT_OFFSET_DB_L) ? CONFIG.INPUT_OFFSET_DB_L : -5,
|
inputOffsetDbL: Number.isFinite(CONFIG.INPUT_OFFSET_DB_L) ? CONFIG.INPUT_OFFSET_DB_L : -5,
|
||||||
inputOffsetDbR: Number.isFinite(CONFIG.INPUT_OFFSET_DB_R) ? CONFIG.INPUT_OFFSET_DB_R : -5,
|
inputOffsetDbR: Number.isFinite(CONFIG.INPUT_OFFSET_DB_R) ? CONFIG.INPUT_OFFSET_DB_R : -5,
|
||||||
|
|||||||
+14
-4
@@ -62,8 +62,9 @@ const CONFIG = {
|
|||||||
RTA_DETECTOR: 'average', // 'average' | 'peak'
|
RTA_DETECTOR: 'average', // 'average' | 'peak'
|
||||||
RTA_INTEGRATION: 'fast', // 'impulse' | 'fast' | 'medium' | 'slow'
|
RTA_INTEGRATION: 'fast', // 'impulse' | 'fast' | 'medium' | 'slow'
|
||||||
RTA_BALLISTICS_MODE: 'average', // 'average' | 'peak' | 'both'
|
RTA_BALLISTICS_MODE: 'average', // 'average' | 'peak' | 'both'
|
||||||
RTA_PEAK_HOLD_MODE: 'auto', // 'off' | 'auto' | 'manual'
|
RTA_PEAK_HOLD_MODE: 'fall', // 'off' | 'auto' | 'fall' | 'manual'
|
||||||
RTA_PEAK_HOLD_SEC: 2.5,
|
RTA_PEAK_HOLD_SEC: 2.5,
|
||||||
|
RTA_PEAK_DECAY_DB_PER_S: 20,
|
||||||
RTA_PEAK_RESET_TOKEN: 0,
|
RTA_PEAK_RESET_TOKEN: 0,
|
||||||
RTA_DISPLAY_HOLD_SEC: 0,
|
RTA_DISPLAY_HOLD_SEC: 0,
|
||||||
RTA_IIR_ORDER: 6,
|
RTA_IIR_ORDER: 6,
|
||||||
@@ -683,6 +684,7 @@ const VU_OFFSET_ALIGNMENT_MIGRATION_KEY = 'migrate_vu_offset_alignment_plus2_v1'
|
|||||||
const INPUT_OFFSET_DB_MIN = -60;
|
const INPUT_OFFSET_DB_MIN = -60;
|
||||||
const INPUT_OFFSET_DB_MAX = 20;
|
const INPUT_OFFSET_DB_MAX = 20;
|
||||||
const LR_DELAY_ZERO_MIGRATION_KEY = 'migrate_lr_delay_zero_to_0_05_v1';
|
const LR_DELAY_ZERO_MIGRATION_KEY = 'migrate_lr_delay_zero_to_0_05_v1';
|
||||||
|
const RTA_PEAK_FALL_DEFAULT_MIGRATION_KEY = 'migrate_rta_peak_fall_default_v1';
|
||||||
let configLoadedOnce = false;
|
let configLoadedOnce = false;
|
||||||
|
|
||||||
function normalizeSoftwarePresetId(value) {
|
function normalizeSoftwarePresetId(value) {
|
||||||
@@ -774,9 +776,16 @@ function loadConfig(opts = {}) {
|
|||||||
let saved = null;
|
let saved = null;
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(STORAGE_KEY);
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
if (!raw) return;
|
if (!raw) {
|
||||||
|
localStorage.setItem(RTA_PEAK_FALL_DEFAULT_MIGRATION_KEY, '1');
|
||||||
|
return;
|
||||||
|
}
|
||||||
saved = JSON.parse(raw);
|
saved = JSON.parse(raw);
|
||||||
for (const k of Object.keys(saved)) if (k in CONFIG) CONFIG[k] = saved[k];
|
for (const k of Object.keys(saved)) if (k in CONFIG) CONFIG[k] = saved[k];
|
||||||
|
if (!localStorage.getItem(RTA_PEAK_FALL_DEFAULT_MIGRATION_KEY)) {
|
||||||
|
if (CONFIG.RTA_PEAK_HOLD_MODE === 'auto') CONFIG.RTA_PEAK_HOLD_MODE = 'fall';
|
||||||
|
localStorage.setItem(RTA_PEAK_FALL_DEFAULT_MIGRATION_KEY, '1');
|
||||||
|
}
|
||||||
if (typeof saved.PPM_TOP === 'number' && !('PPM_EBU_TOP' in saved)) CONFIG.PPM_EBU_TOP = saved.PPM_TOP;
|
if (typeof saved.PPM_TOP === 'number' && !('PPM_EBU_TOP' in saved)) CONFIG.PPM_EBU_TOP = saved.PPM_TOP;
|
||||||
if (typeof saved.PPM_BOTTOM === 'number' && !('PPM_EBU_BOTTOM' in saved)) CONFIG.PPM_EBU_BOTTOM = saved.PPM_BOTTOM;
|
if (typeof saved.PPM_BOTTOM === 'number' && !('PPM_EBU_BOTTOM' in saved)) CONFIG.PPM_EBU_BOTTOM = saved.PPM_BOTTOM;
|
||||||
if (typeof saved.PPM_RED_START === 'number' && !('PPM_EBU_RED_START' in saved)) CONFIG.PPM_EBU_RED_START = saved.PPM_RED_START;
|
if (typeof saved.PPM_RED_START === 'number' && !('PPM_EBU_RED_START' in saved)) CONFIG.PPM_EBU_RED_START = saved.PPM_RED_START;
|
||||||
@@ -810,9 +819,10 @@ function loadConfig(opts = {}) {
|
|||||||
const hold = Number(CONFIG.RTA_PEAK_HOLD_SEC);
|
const hold = Number(CONFIG.RTA_PEAK_HOLD_SEC);
|
||||||
CONFIG.RTA_PEAK_HOLD_SEC = hold >= 3.25 ? 4.0 : 2.5;
|
CONFIG.RTA_PEAK_HOLD_SEC = hold >= 3.25 ? 4.0 : 2.5;
|
||||||
}
|
}
|
||||||
CONFIG.RTA_PEAK_HOLD_MODE = ['off', 'auto', 'manual'].includes(CONFIG.RTA_PEAK_HOLD_MODE)
|
CONFIG.RTA_PEAK_HOLD_MODE = ['off', 'auto', 'fall', 'manual'].includes(CONFIG.RTA_PEAK_HOLD_MODE)
|
||||||
? CONFIG.RTA_PEAK_HOLD_MODE
|
? CONFIG.RTA_PEAK_HOLD_MODE
|
||||||
: 'auto';
|
: 'fall';
|
||||||
|
CONFIG.RTA_PEAK_DECAY_DB_PER_S = Math.max(1, Math.min(60, Number(CONFIG.RTA_PEAK_DECAY_DB_PER_S) || 20));
|
||||||
CONFIG.RTA_PEAK_RESET_TOKEN = Math.max(0, Math.floor(Number(CONFIG.RTA_PEAK_RESET_TOKEN) || 0));
|
CONFIG.RTA_PEAK_RESET_TOKEN = Math.max(0, Math.floor(Number(CONFIG.RTA_PEAK_RESET_TOKEN) || 0));
|
||||||
if (!Number.isFinite(CONFIG.DBFS_TOP)) CONFIG.DBFS_TOP = CONFIG_DEFAULTS.DBFS_TOP;
|
if (!Number.isFinite(CONFIG.DBFS_TOP)) CONFIG.DBFS_TOP = CONFIG_DEFAULTS.DBFS_TOP;
|
||||||
if (!Number.isFinite(CONFIG.DBFS_BOTTOM)) CONFIG.DBFS_BOTTOM = CONFIG_DEFAULTS.DBFS_BOTTOM;
|
if (!Number.isFinite(CONFIG.DBFS_BOTTOM)) CONFIG.DBFS_BOTTOM = CONFIG_DEFAULTS.DBFS_BOTTOM;
|
||||||
|
|||||||
+7
-2
@@ -386,15 +386,20 @@
|
|||||||
<div class="opt"><label>Hold Mode</label>
|
<div class="opt"><label>Hold Mode</label>
|
||||||
<select id="opt_rtaHoldMode" style="width:150px">
|
<select id="opt_rtaHoldMode" style="width:150px">
|
||||||
<option value="off">Aus</option>
|
<option value="off">Aus</option>
|
||||||
<option value="auto" selected>Auto</option>
|
<option value="auto">Hold</option>
|
||||||
|
<option value="fall" selected>Hold + Fall</option>
|
||||||
<option value="manual">Manuell</option>
|
<option value="manual">Manuell</option>
|
||||||
</select>
|
</select>
|
||||||
<small>Auto gibt den Peak nach 2,5 oder 4 Sekunden direkt auf den aktuellen Messwert frei; Manuell hält bis zum Reset.</small>
|
<small>Hold gibt nach Ablauf direkt frei; Hold + Fall läuft danach weich zurück; Manuell hält bis zum Reset.</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="opt"><label>Hold Time (s)</label>
|
<div class="opt"><label>Hold Time (s)</label>
|
||||||
<input id="opt_rtaHoldTime" type="number" min="0" max="30" step="0.5" style="width:110px">
|
<input id="opt_rtaHoldTime" type="number" min="0" max="30" step="0.5" style="width:110px">
|
||||||
<small>Dauer der Hold-Anzeige</small>
|
<small>Dauer der Hold-Anzeige</small>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="opt"><label>Hold Fall (dB/s)</label>
|
||||||
|
<input id="opt_rtaDecay" type="number" min="1" max="60" step="1" style="width:110px">
|
||||||
|
<small>Rücklaufgeschwindigkeit im Modus Hold + Fall</small>
|
||||||
|
</div>
|
||||||
<div class="opt"><label>Display Hold (s)</label>
|
<div class="opt"><label>Display Hold (s)</label>
|
||||||
<input id="opt_rtaDisplayHold" type="number" min="0" max="5" step="0.1" style="width:110px">
|
<input id="opt_rtaDisplayHold" type="number" min="0" max="5" step="0.1" style="width:110px">
|
||||||
<small>Zusatzhalt für Anzeige</small>
|
<small>Zusatzhalt für Anzeige</small>
|
||||||
|
|||||||
+4
-2
@@ -293,6 +293,7 @@ function syncUI() {
|
|||||||
['opt_rtaBallistics', CONFIG.RTA_BALLISTICS_MODE || 'average'],
|
['opt_rtaBallistics', CONFIG.RTA_BALLISTICS_MODE || 'average'],
|
||||||
['opt_rtaHoldMode', CONFIG.RTA_PEAK_HOLD_MODE || 'auto'],
|
['opt_rtaHoldMode', CONFIG.RTA_PEAK_HOLD_MODE || 'auto'],
|
||||||
['opt_rtaHoldTime', CONFIG.RTA_PEAK_HOLD_SEC ?? 2.5],
|
['opt_rtaHoldTime', CONFIG.RTA_PEAK_HOLD_SEC ?? 2.5],
|
||||||
|
['opt_rtaDecay', CONFIG.RTA_PEAK_DECAY_DB_PER_S ?? 20],
|
||||||
['opt_rtaDisplayHold', CONFIG.RTA_DISPLAY_HOLD_SEC ?? 0],
|
['opt_rtaDisplayHold', CONFIG.RTA_DISPLAY_HOLD_SEC ?? 0],
|
||||||
['opt_spectroGamma', CONFIG.SPECTRO_GAMMA ?? 0.9, 'val_spectroGamma', (v)=>Number(v).toFixed(2)],
|
['opt_spectroGamma', CONFIG.SPECTRO_GAMMA ?? 0.9, 'val_spectroGamma', (v)=>Number(v).toFixed(2)],
|
||||||
['opt_spectroScroll', CONFIG.SPECTRO_SCROLL_MODE ?? 1],
|
['opt_spectroScroll', CONFIG.SPECTRO_SCROLL_MODE ?? 1],
|
||||||
@@ -1164,9 +1165,10 @@ function wireHandlers(env) {
|
|||||||
return val;
|
return val;
|
||||||
});
|
});
|
||||||
h('opt_rtaHoldMode', v => {
|
h('opt_rtaHoldMode', v => {
|
||||||
const allowed = new Set(['off','auto','manual']);
|
const allowed = new Set(['off','auto','fall','manual']);
|
||||||
CONFIG.RTA_PEAK_HOLD_MODE = allowed.has(v) ? v : 'auto';
|
CONFIG.RTA_PEAK_HOLD_MODE = allowed.has(v) ? v : 'fall';
|
||||||
});
|
});
|
||||||
|
h('opt_rtaDecay', v => { CONFIG.RTA_PEAK_DECAY_DB_PER_S = clamp(+v, 1, 60); });
|
||||||
h('opt_rtaHoldTime', v => {
|
h('opt_rtaHoldTime', v => {
|
||||||
const requested = clamp(+v, 0, 30);
|
const requested = clamp(+v, 0, 30);
|
||||||
CONFIG.RTA_PEAK_HOLD_SEC = CONFIG.RTA_BAR_LAYOUT === 'rtw'
|
CONFIG.RTA_PEAK_HOLD_SEC = CONFIG.RTA_BAR_LAYOUT === 'rtw'
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const STATIC_STROKE_PAD = 2;
|
|||||||
const PEAK_HOLD_MAP = new Map(Object.entries({
|
const PEAK_HOLD_MAP = new Map(Object.entries({
|
||||||
off: 0,
|
off: 0,
|
||||||
auto: null,
|
auto: null,
|
||||||
|
fall: null,
|
||||||
'1s': 1,
|
'1s': 1,
|
||||||
'2s': 2,
|
'2s': 2,
|
||||||
'4s': 4,
|
'4s': 4,
|
||||||
@@ -290,6 +291,7 @@ function buildConfigSignature(CONFIG, nyq, binCount) {
|
|||||||
CONFIG.RTA_INTEGRATION,
|
CONFIG.RTA_INTEGRATION,
|
||||||
CONFIG.RTA_PEAK_HOLD_MODE,
|
CONFIG.RTA_PEAK_HOLD_MODE,
|
||||||
CONFIG.RTA_PEAK_HOLD_SEC,
|
CONFIG.RTA_PEAK_HOLD_SEC,
|
||||||
|
CONFIG.RTA_PEAK_DECAY_DB_PER_S,
|
||||||
CONFIG.RTA_PEAK_RESET_TOKEN,
|
CONFIG.RTA_PEAK_RESET_TOKEN,
|
||||||
CONFIG.RTA_DISPLAY_HOLD_SEC,
|
CONFIG.RTA_DISPLAY_HOLD_SEC,
|
||||||
CONFIG.REALTIME_RENDER_STYLE,
|
CONFIG.REALTIME_RENDER_STYLE,
|
||||||
@@ -413,13 +415,15 @@ function applyIntegration(state, levels, CONFIG, range) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyPeakHold(state, levels, CONFIG, range) {
|
function applyPeakHold(state, levels, CONFIG, range) {
|
||||||
const mode = CONFIG.RTA_PEAK_HOLD_MODE || 'auto';
|
const mode = CONFIG.RTA_PEAK_HOLD_MODE || 'fall';
|
||||||
const len = levels.length;
|
const len = levels.length;
|
||||||
const mapped = PEAK_HOLD_MAP.get(mode);
|
const mapped = PEAK_HOLD_MAP.get(mode);
|
||||||
const holdSec = Number.isFinite(mapped)
|
const holdSec = Number.isFinite(mapped)
|
||||||
? Math.max(0, mapped)
|
? Math.max(0, mapped)
|
||||||
: Math.max(0, Number(CONFIG.RTA_PEAK_HOLD_SEC) || 0);
|
: Math.max(0, Number(CONFIG.RTA_PEAK_HOLD_SEC) || 0);
|
||||||
|
const decayRate = Math.max(0, Number(CONFIG.RTA_PEAK_DECAY_DB_PER_S) || 20);
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
|
const dt = Math.max(0, (now - (state.holdSampleTs || now)) / 1000);
|
||||||
state.holdSampleTs = now;
|
state.holdSampleTs = now;
|
||||||
|
|
||||||
if (!state.peakHold || state.peakHold.length !== len) {
|
if (!state.peakHold || state.peakHold.length !== len) {
|
||||||
@@ -445,6 +449,8 @@ function applyPeakHold(state, levels, CONFIG, range) {
|
|||||||
if (mode === 'auto' && (now - (state.lastPeakTime[i] || 0)) >= holdSec * 1000) {
|
if (mode === 'auto' && (now - (state.lastPeakTime[i] || 0)) >= holdSec * 1000) {
|
||||||
buffer[i] = current;
|
buffer[i] = current;
|
||||||
state.lastPeakTime[i] = now;
|
state.lastPeakTime[i] = now;
|
||||||
|
} else if (mode === 'fall' && (now - (state.lastPeakTime[i] || 0)) >= holdSec * 1000) {
|
||||||
|
buffer[i] = Math.max(current, buffer[i] - decayRate * dt);
|
||||||
}
|
}
|
||||||
buffer[i] = clamp(buffer[i], range.bottom, range.top);
|
buffer[i] = clamp(buffer[i], range.bottom, range.top);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user