Add selectable RTA filter characteristics

This commit is contained in:
Mikei386
2026-07-21 21:12:19 +02:00
parent af974b92ec
commit 3478bfb495
8 changed files with 116 additions and 18 deletions
+34 -14
View File
@@ -21,9 +21,9 @@ use crate::model::{RtaFrame, SpectroFrame, WaveEnvFrame};
use crate::ppm::{PpmDetector, PpmStandard}; use crate::ppm::{PpmDetector, PpmStandard};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use crate::rta::{ use crate::rta::{
design_fractional_octave_band, exact_fractional_octave_center, fractional_octave_edges, design_fractional_octave_band, design_legacy_repeated_band, exact_fractional_octave_center,
integrate_power, normalize_weighting, weighting_power_correction, weighting_reference_db, fractional_octave_edges, integrate_power, normalize_weighting, weighting_power_correction,
FrequencyWeighting, StereoCascade, RTW_THIRD_OCTAVE_CENTERS, weighting_reference_db, FrequencyWeighting, StereoCascade, RTW_THIRD_OCTAVE_CENTERS,
}; };
use crate::state::NativeWavRecorder; use crate::state::NativeWavRecorder;
use crate::{ use crate::{
@@ -1329,8 +1329,9 @@ fn build_meter_frame(
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn ensure_rta_state(state: &mut PpmState, sample_rate: u32, config: &PhoenixRtaConfig) { fn ensure_rta_state(state: &mut PpmState, sample_rate: u32, config: &PhoenixRtaConfig) {
let signature = format!( let signature = format!(
"{}|{}|{}|{}|{}|{}|{}|{}", "{}|{}|{}|{}|{}|{}|{}|{}|{}",
normalize_engine(&config.engine), normalize_engine(&config.engine),
normalize_filterbank(&config.filterbank),
normalize_layout(&config.layout), normalize_layout(&config.layout),
normalize_bpo_key(&config.bpo), normalize_bpo_key(&config.bpo),
normalize_freq_range(&config.freq_range), normalize_freq_range(&config.freq_range),
@@ -1439,9 +1440,9 @@ fn build_active_rta_bands(
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn create_rta_bank(sample_rate: u32, config: &PhoenixRtaConfig) -> RtaBank { fn create_rta_bank(sample_rate: u32, config: &PhoenixRtaConfig) -> RtaBank {
let (active_layout, bpo_key, active_bands) = build_active_rta_bands(sample_rate, config); let (active_layout, bpo_key, active_bands) = build_active_rta_bands(sample_rate, config);
// The RTW profile uses a third-order Butterworth prototype, resulting in // In Butterworth mode the RTW profile uses a third-order prototype,
// a sixth-order bandpass. Phoenix extension profiles retain selectable // resulting in a sixth-order bandpass. Phoenix extension profiles retain
// even total orders. // selectable even total orders.
let prototype_order = if active_layout == "rtw" { let prototype_order = if active_layout == "rtw" {
3 3
} else { } else {
@@ -1461,13 +1462,22 @@ fn create_rta_bank(sample_rate: u32, config: &PhoenixRtaConfig) -> RtaBank {
.unwrap_or(20_000.0); .unwrap_or(20_000.0);
let mut filters = Vec::with_capacity(active_bands.len()); let mut filters = Vec::with_capacity(active_bands.len());
for band in &active_bands { for band in &active_bands {
let coefficients = design_fractional_octave_band( let coefficients = if normalize_filterbank(&config.filterbank) == "legacy" {
band.measurement_center, design_legacy_repeated_band(
band.f_lo, band.center,
band.f_hi, sample_rate,
sample_rate, bpo_value(bpo_key),
prototype_order, config.order.clamp(2, 8) as usize,
); )
} else {
design_fractional_octave_band(
band.measurement_center,
band.f_lo,
band.f_hi,
sample_rate,
prototype_order,
)
};
filters.push(StereoCascade::new(coefficients)); filters.push(StereoCascade::new(coefficients));
} }
let len = active_bands.len(); let len = active_bands.len();
@@ -1739,6 +1749,7 @@ fn finalize_spectro_state(state: &mut SpectroState, sample_rate: u32) -> bool {
fn build_iir_rta_frame(bank: &RtaBank, sample_rate: u32, config: &PhoenixRtaConfig) -> RtaFrame { fn build_iir_rta_frame(bank: &RtaBank, sample_rate: u32, config: &PhoenixRtaConfig) -> RtaFrame {
RtaFrame { RtaFrame {
engine: "iir".to_string(), engine: "iir".to_string(),
filterbank: normalize_filterbank(&config.filterbank).to_string(),
bands_avg: bank.levels.clone(), bands_avg: bank.levels.clone(),
bands_peak: bank.peaks.clone(), bands_peak: bank.peaks.clone(),
centers: bank.centers.clone(), centers: bank.centers.clone(),
@@ -1759,6 +1770,7 @@ fn build_fft_rta_frame(
) -> RtaFrame { ) -> RtaFrame {
RtaFrame { RtaFrame {
engine: "fft".to_string(), engine: "fft".to_string(),
filterbank: "none".to_string(),
bands_avg: state.levels.clone(), bands_avg: state.levels.clone(),
bands_peak: state.peaks.clone(), bands_peak: state.peaks.clone(),
centers: state.centers.clone(), centers: state.centers.clone(),
@@ -1803,6 +1815,14 @@ fn normalize_engine(value: &str) -> &'static str {
} }
} }
#[cfg(target_os = "linux")]
fn normalize_filterbank(value: &str) -> &'static str {
match value.trim().to_ascii_lowercase().as_str() {
"butterworth" | "butter" => "butterworth",
_ => "legacy",
}
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn normalize_fft_size(value: u32) -> usize { fn normalize_fft_size(value: u32) -> usize {
match value { match value {
+3
View File
@@ -19,6 +19,7 @@ pub struct ServiceStatus {
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct RtaFrame { pub struct RtaFrame {
pub engine: String, pub engine: String,
pub filterbank: String,
pub bands_avg: Vec<f32>, pub bands_avg: Vec<f32>,
pub bands_peak: Vec<f32>, pub bands_peak: Vec<f32>,
pub centers: Vec<f32>, pub centers: Vec<f32>,
@@ -51,6 +52,7 @@ pub struct WaveEnvFrame {
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
pub struct PhoenixRtaConfig { pub struct PhoenixRtaConfig {
pub engine: String, pub engine: String,
pub filterbank: String,
pub fft_size: u32, pub fft_size: u32,
pub mono_input: bool, pub mono_input: bool,
pub lr_fractional_delay_enabled: bool, pub lr_fractional_delay_enabled: bool,
@@ -81,6 +83,7 @@ impl Default for PhoenixRtaConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
engine: "iir".to_string(), engine: "iir".to_string(),
filterbank: "legacy".to_string(),
fft_size: 8192, fft_size: 8192,
mono_input: false, mono_input: false,
lr_fractional_delay_enabled: true, lr_fractional_delay_enabled: true,
+52 -3
View File
@@ -1,8 +1,7 @@
//! Realtime-analyzer filter primitives. //! Realtime-analyzer filter primitives.
//! //!
//! Fractional-octave bands are designed as complete Butterworth bandpasses: //! Provides both the original repeated-RBJ Phoenix characteristic and complete
//! an analog low-pass prototype is transformed to a bandpass, pre-warped and //! Butterworth fractional-octave bandpasses for controlled comparison.
//! mapped with the bilinear transform, then emitted as distinct SOS sections.
pub const RTW_THIRD_OCTAVE_CENTERS: &[f32] = &[ pub const RTW_THIRD_OCTAVE_CENTERS: &[f32] = &[
20.0, 25.0, 31.5, 40.0, 50.0, 63.0, 80.0, 100.0, 125.0, 160.0, 200.0, 250.0, 315.0, 400.0, 20.0, 25.0, 31.5, 40.0, 50.0, 63.0, 80.0, 100.0, 125.0, 160.0, 200.0, 250.0, 315.0, 400.0,
@@ -155,6 +154,35 @@ pub fn design_fractional_octave_band(
sections sections
} }
/// Reproduces the original Phoenix analyzer characteristic: one constant-peak
/// RBJ bandpass is repeated for every second order of the requested cascade.
/// It is retained for comparison with existing Phoenix/RTW captures and is
/// not presented as a filter implementation prescribed by IEC 61260.
pub fn design_legacy_repeated_band(
center_hz: f32,
sample_rate: u32,
bands_per_octave: usize,
total_order: usize,
) -> Vec<BiquadCoeffs> {
let fs = f64::from(sample_rate.max(8_000));
let center = f64::from(center_hz).clamp(0.01, fs * 0.499_9);
let bpo = bands_per_octave.max(1) as f64;
let upper = 2.0f64.powf(1.0 / (2.0 * bpo));
let lower = 2.0f64.powf(-1.0 / (2.0 * bpo));
let q = 1.0 / (upper - lower);
let omega = 2.0 * std::f64::consts::PI * center / fs;
let alpha = omega.sin() / (2.0 * q);
let a0 = 1.0 + alpha;
let section = BiquadCoeffs {
b0: alpha / a0,
b1: 0.0,
b2: -alpha / a0,
a1: -2.0 * omega.cos() / a0,
a2: (1.0 - alpha) / a0,
};
vec![section; (total_order.clamp(2, 8) / 2).max(1)]
}
pub fn cascade_magnitude(sections: &[BiquadCoeffs], freq_hz: f32, sample_rate: u32) -> f64 { pub fn cascade_magnitude(sections: &[BiquadCoeffs], freq_hz: f32, sample_rate: u32) -> f64 {
let omega = 2.0 * std::f64::consts::PI * f64::from(freq_hz) / f64::from(sample_rate.max(8_000)); let omega = 2.0 * std::f64::consts::PI * f64::from(freq_hz) / f64::from(sample_rate.max(8_000));
let z1 = Complex::new(omega.cos(), -omega.sin()); let z1 = Complex::new(omega.cos(), -omega.sin());
@@ -396,6 +424,27 @@ mod tests {
assert!(((lower * upper).sqrt() - center).abs() < 0.001); assert!(((lower * upper).sqrt() - center).abs() < 0.001);
} }
#[test]
fn legacy_sixth_order_repeats_three_identical_sections() {
let sections = design_legacy_repeated_band(1_000.0, 48_000, 6, 6);
assert_eq!(sections.len(), 3);
assert_eq!(sections[0].b0, sections[1].b0);
assert_eq!(sections[1].a2, sections[2].a2);
assert!(cascade_db(&sections, 1_000.0, 48_000).abs() < 0.001);
}
#[test]
fn legacy_and_butterworth_characteristics_remain_distinct() {
let factor = 2.0f32.powf(1.0 / 12.0);
let legacy = design_legacy_repeated_band(1_000.0, 48_000, 6, 6);
let butterworth =
design_fractional_octave_band(1_000.0, 1_000.0 / factor, 1_000.0 * factor, 48_000, 3);
let legacy_lower = cascade_db(&legacy, 1_000.0 / factor, 48_000);
let butterworth_lower = cascade_db(&butterworth, 1_000.0 / factor, 48_000);
assert!(legacy_lower < -8.5);
assert!((butterworth_lower + 3.0103).abs() < 0.08);
}
#[test] #[test]
fn neighboring_third_octave_centers_are_suppressed() { fn neighboring_third_octave_centers_are_suppressed() {
let factor = 2.0f32.powf(1.0 / 6.0); let factor = 2.0f32.powf(1.0 / 6.0);
+7
View File
@@ -321,6 +321,10 @@ fn normalize_rta_config(mut config: PhoenixRtaConfig) -> PhoenixRtaConfig {
"fft" => "fft".to_string(), "fft" => "fft".to_string(),
_ => "iir".to_string(), _ => "iir".to_string(),
}; };
config.filterbank = match config.filterbank.trim().to_ascii_lowercase().as_str() {
"butterworth" | "butter" => "butterworth".to_string(),
_ => "legacy".to_string(),
};
config.fft_size = match config.fft_size { config.fft_size = match config.fft_size {
2048 | 4096 | 8192 | 16384 => config.fft_size, 2048 | 4096 | 8192 | 16384 => config.fft_size,
n if n < 3072 => 2048, n if n < 3072 => 2048,
@@ -721,6 +725,7 @@ mod tests {
..PhoenixRtaConfig::default() ..PhoenixRtaConfig::default()
}); });
assert_eq!(config.engine, "iir"); assert_eq!(config.engine, "iir");
assert_eq!(config.filterbank, "legacy");
assert_eq!(config.bpo, "1_12"); assert_eq!(config.bpo, "1_12");
assert_eq!(config.order, 6); assert_eq!(config.order, 6);
assert_eq!(config.freq_range, "norm"); assert_eq!(config.freq_range, "norm");
@@ -730,6 +735,7 @@ mod tests {
fn phoenix_profile_retains_explicit_extensions() { fn phoenix_profile_retains_explicit_extensions() {
let config = normalize_rta_config(PhoenixRtaConfig { let config = normalize_rta_config(PhoenixRtaConfig {
engine: "fft".to_string(), engine: "fft".to_string(),
filterbank: "butterworth".to_string(),
bpo: "1_12".to_string(), bpo: "1_12".to_string(),
order: 8, order: 8,
freq_range: "lf".to_string(), freq_range: "lf".to_string(),
@@ -738,6 +744,7 @@ mod tests {
..PhoenixRtaConfig::default() ..PhoenixRtaConfig::default()
}); });
assert_eq!(config.engine, "fft"); assert_eq!(config.engine, "fft");
assert_eq!(config.filterbank, "butterworth");
assert_eq!(config.bpo, "1_12"); assert_eq!(config.bpo, "1_12");
assert_eq!(config.order, 8); assert_eq!(config.order, 8);
assert_eq!(config.freq_range, "lf"); assert_eq!(config.freq_range, "lf");
+2
View File
@@ -516,6 +516,7 @@ function buildPhoenixMeterPacket(frame) {
xyR, xyR,
rta: rta ? { rta: rta ? {
engine: String(rta?.engine || 'iir'), engine: String(rta?.engine || 'iir'),
filterbank: String(rta?.filterbank || 'legacy'),
bands_avg: Array.isArray(rta?.bands_avg) ? rta.bands_avg : (Array.isArray(rta?.bands) ? rta.bands : []), bands_avg: Array.isArray(rta?.bands_avg) ? rta.bands_avg : (Array.isArray(rta?.bands) ? rta.bands : []),
bands_peak: Array.isArray(rta?.bands_peak) ? rta.bands_peak : [], bands_peak: Array.isArray(rta?.bands_peak) ? rta.bands_peak : [],
bands: Array.isArray(rta?.bands) ? rta.bands : (Array.isArray(rta?.bands_avg) ? rta.bands_avg : []), bands: Array.isArray(rta?.bands) ? rta.bands : (Array.isArray(rta?.bands_avg) ? rta.bands_avg : []),
@@ -871,6 +872,7 @@ export function buildRtaRuntimeConfig(CONFIG = {}) {
const runtimeBpoMode = bpoMode; const runtimeBpoMode = bpoMode;
return { return {
engine: rtwProfile ? 'iir' : (CONFIG.RTA_ENGINE || 'iir'), engine: rtwProfile ? 'iir' : (CONFIG.RTA_ENGINE || 'iir'),
filterbank: CONFIG.RTA_IIR_FILTERBANK === 'butterworth' ? 'butterworth' : 'legacy',
fftSize: CONFIG.FFT_SIZE || 4096, fftSize: CONFIG.FFT_SIZE || 4096,
monoInput: !!CONFIG.MONO_INPUT, monoInput: !!CONFIG.MONO_INPUT,
lrFractionalDelayEnabled: !!CONFIG.LR_FRACTIONAL_DELAY_ENABLED, lrFractionalDelayEnabled: !!CONFIG.LR_FRACTIONAL_DELAY_ENABLED,
+4
View File
@@ -50,6 +50,7 @@ const CONFIG = {
REALTIME_BAR_HOLD_MS: 800, REALTIME_BAR_HOLD_MS: 800,
REALTIME_BAR_DECAY_DB_PER_S: 20, REALTIME_BAR_DECAY_DB_PER_S: 20,
RTA_ENGINE: 'iir', // 'fft' | 'iir' RTA_ENGINE: 'iir', // 'fft' | 'iir'
RTA_IIR_FILTERBANK: 'legacy', // 'legacy' | 'butterworth'
RTA_FREQ_RANGE: 'norm', // 'norm' | 'lf' RTA_FREQ_RANGE: 'norm', // 'norm' | 'lf'
RTA_BPO_MODE: '1_3', RTA_BPO_MODE: '1_3',
RTA_WEIGHTING: 'z', // 'z' | 'a' | 'c' RTA_WEIGHTING: 'z', // 'z' | 'a' | 'c'
@@ -784,6 +785,9 @@ function loadConfig(opts = {}) {
if (typeof saved.PPM_DIN_DECAY_DB_PER_S === 'number') CONFIG.PPM_DIN_DECAY_DB_PER_S = saved.PPM_DIN_DECAY_DB_PER_S; if (typeof saved.PPM_DIN_DECAY_DB_PER_S === 'number') CONFIG.PPM_DIN_DECAY_DB_PER_S = saved.PPM_DIN_DECAY_DB_PER_S;
if (typeof saved.PPM_DIN_HOLD_MS === 'number') CONFIG.PPM_DIN_HOLD_MS = saved.PPM_DIN_HOLD_MS; if (typeof saved.PPM_DIN_HOLD_MS === 'number') CONFIG.PPM_DIN_HOLD_MS = saved.PPM_DIN_HOLD_MS;
if (CONFIG.RTA_BAR_LAYOUT === 'classic') CONFIG.RTA_BAR_LAYOUT = 'rtw'; if (CONFIG.RTA_BAR_LAYOUT === 'classic') CONFIG.RTA_BAR_LAYOUT = 'rtw';
CONFIG.RTA_IIR_FILTERBANK = CONFIG.RTA_IIR_FILTERBANK === 'butterworth'
? 'butterworth'
: 'legacy';
if (CONFIG.RTA_BAR_LAYOUT === 'rtw') { if (CONFIG.RTA_BAR_LAYOUT === 'rtw') {
CONFIG.RTA_ENGINE = 'iir'; CONFIG.RTA_ENGINE = 'iir';
CONFIG.RTA_IIR_ORDER = 6; CONFIG.RTA_IIR_ORDER = 6;
+9 -1
View File
@@ -303,6 +303,14 @@
</select> </select>
<small>FFT (fix) oder IIR-Filterbank</small> <small>FFT (fix) oder IIR-Filterbank</small>
</div> </div>
<div class="opt">
<label>IIR-Filtercharakteristik</label>
<select id="opt_rtaFilterbank" style="width:230px">
<option value="legacy" selected>Legacy (Phoenix/RTW-Vergleich)</option>
<option value="butterworth">Butterworth 6. Ordnung</option>
</select>
<small>Beide Varianten sind IIR. Legacy bildet die bisherige Anzeige nach; Butterworth ist die neue mathematische Konstruktion.</small>
</div>
<div class="opt"> <div class="opt">
<label>Bänder pro Oktave</label> <label>Bänder pro Oktave</label>
<select id="opt_rtaBpo" style="width:150px"> <select id="opt_rtaBpo" style="width:150px">
@@ -1258,7 +1266,7 @@
<li><strong>Browserlast:</strong> Single-Slot-Puffer und Sequenzprüfung für Mess-, Spektrogramm- und Visualisierungsdaten ergänzt. Große Rohsamplefelder werden nicht mehr in jedem JSON-Messpaket wiederholt.</li> <li><strong>Browserlast:</strong> Single-Slot-Puffer und Sequenzprüfung für Mess-, Spektrogramm- und Visualisierungsdaten ergänzt. Große Rohsamplefelder werden nicht mehr in jedem JSON-Messpaket wiederholt.</li>
<li><strong>Spektrogramm:</strong> Langzeitstillstand und stotterndes Nachholen beseitigt. Inkrementelles Spaltenzeichnen, Worker-ACK, Watchdog/Neustart und begrenztes Überspringen veralteter Spalten ergänzt. 0,5×, 1×, 2×, 4× und 6× besitzen nun eine feste, FFT- und DPI-unabhängige Zeitbasis.</li> <li><strong>Spektrogramm:</strong> Langzeitstillstand und stotterndes Nachholen beseitigt. Inkrementelles Spaltenzeichnen, Worker-ACK, Watchdog/Neustart und begrenztes Überspringen veralteter Spalten ergänzt. 0,5×, 1×, 2×, 4× und 6× besitzen nun eine feste, FFT- und DPI-unabhängige Zeitbasis.</li>
<li><strong>DIN-/EBU-PPM:</strong> Blockunabhängige Quasi-Peak-Detektoren mit bandbegrenzter Interpolation, korrekter Attack- und Rücklaufballistik sowie automatischen Tonburst-, Frequenzgang- und Polaritätstests implementiert. Eine zweite Browser-Anstiegsballistik wurde entfernt.</li> <li><strong>DIN-/EBU-PPM:</strong> Blockunabhängige Quasi-Peak-Detektoren mit bandbegrenzter Interpolation, korrekter Attack- und Rücklaufballistik sowie automatischen Tonburst-, Frequenzgang- und Polaritätstests implementiert. Eine zweite Browser-Anstiegsballistik wurde entfernt.</li>
<li><strong>RTW-RTA:</strong> RTW-Profil verbindlich auf die IIR-Oktavteilband-Filterbank festgelegt. Die Filterbank verwendet vollständige Butterworth-Bandpässe sechster Ordnung aus unterschiedlichen SOS-Sektionen, interne f64-Rechnung sowie exakte IEC-Basis-10-Mitten und -Bandkanten bei vertrauten Nominalbeschriftungen.</li> <li><strong>RTW-RTA:</strong> RTW-Profil verbindlich auf die IIR-Oktavteilband-Filterbank festgelegt. Bis zur Vergleichsmessung am realen RTW-Gerät sind die bisherige Phoenix/RTW-Charakteristik und vollständige Butterworth-Bandpässe sechster Ordnung getrennt auswählbar.</li>
<li><strong>RTA-Bewertung und Integration:</strong> A-, C- und Z-Bewertung sampleweise vor der Filterbank ergänzt. Fast, Medium, Slow, Impulse, Average und Peak rechnen im Leistungsbereich und unabhängig von der ALSA-Periodengröße.</li> <li><strong>RTA-Bewertung und Integration:</strong> A-, C- und Z-Bewertung sampleweise vor der Filterbank ergänzt. Fast, Medium, Slow, Impulse, Average und Peak rechnen im Leistungsbereich und unabhängig von der ALSA-Periodengröße.</li>
<li><strong>RTA-Auflösung:</strong> Umschaltung zwischen 1/3, 1/6 und 1/12 repariert. Alle drei Auflösungen bleiben im RTW-Profil aktiv und verwenden dessen IIR-Filterbank, Darstellung und Ballistik.</li> <li><strong>RTA-Auflösung:</strong> Umschaltung zwischen 1/3, 1/6 und 1/12 repariert. Alle drei Auflösungen bleiben im RTW-Profil aktiv und verwenden dessen IIR-Filterbank, Darstellung und Ballistik.</li>
<li><strong>Hardwareparameter:</strong> Tatsächlich von ALSA ausgehandelte Samplerate, Perioden- und Puffergröße werden nun durch DSP, Aufnahme, Status und Browser verwendet; die feste 48-kHz-Annahme wurde entfernt.</li> <li><strong>Hardwareparameter:</strong> Tatsächlich von ALSA ausgehandelte Samplerate, Perioden- und Puffergröße werden nun durch DSP, Aufnahme, Status und Browser verwendet; die feste 48-kHz-Annahme wurde entfernt.</li>
+5
View File
@@ -278,6 +278,7 @@ function syncUI() {
['opt_barThin', CONFIG.METER_BAR_THIN, 'val_barThin', (v)=>Number(v).toFixed(2)], ['opt_barThin', CONFIG.METER_BAR_THIN, 'val_barThin', (v)=>Number(v).toFixed(2)],
['opt_headerTextColor', CONFIG.HEADER_TEXT_COLOR || '#ffe066'], ['opt_headerTextColor', CONFIG.HEADER_TEXT_COLOR || '#ffe066'],
['opt_rtaEngine', CONFIG.RTA_ENGINE || 'iir'], ['opt_rtaEngine', CONFIG.RTA_ENGINE || 'iir'],
['opt_rtaFilterbank', CONFIG.RTA_IIR_FILTERBANK || 'legacy'],
['opt_rtaBpo', CONFIG.RTA_BPO_MODE || '1_6'], ['opt_rtaBpo', CONFIG.RTA_BPO_MODE || '1_6'],
['opt_rtaOrder', CONFIG.RTA_IIR_ORDER ?? 4], ['opt_rtaOrder', CONFIG.RTA_IIR_ORDER ?? 4],
['opt_rtaDisplayGainFft', CONFIG.RTA_DISPLAY_GAIN_FFT_DB ?? 12, 'val_rtaDisplayGainFft', (v)=>`${v} dB`], ['opt_rtaDisplayGainFft', CONFIG.RTA_DISPLAY_GAIN_FFT_DB ?? 12, 'val_rtaDisplayGainFft', (v)=>`${v} dB`],
@@ -1074,6 +1075,10 @@ function wireHandlers(env) {
CONFIG.RTA_ENGINE = CONFIG.RTA_BAR_LAYOUT === 'rtw' ? 'iir' : ((v === 'iir') ? 'iir' : 'fft'); CONFIG.RTA_ENGINE = CONFIG.RTA_BAR_LAYOUT === 'rtw' ? 'iir' : ((v === 'iir') ? 'iir' : 'fft');
return CONFIG.RTA_ENGINE; return CONFIG.RTA_ENGINE;
}); });
h('opt_rtaFilterbank', v => {
CONFIG.RTA_IIR_FILTERBANK = v === 'butterworth' ? 'butterworth' : 'legacy';
return CONFIG.RTA_IIR_FILTERBANK;
});
h('opt_rtaBpo', v => { h('opt_rtaBpo', v => {
applyRtaBpoSelection(v); applyRtaBpoSelection(v);
const layoutControl = E('opt_rtaLayout'); const layoutControl = E('opt_rtaLayout');