Compare commits
4
Commits
cbbeac5b0f
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec3b6c3059 | ||
|
|
684c5fe3d3 | ||
|
|
07563c3b11 | ||
|
|
48578c5919 |
No files matched your search
@@ -2,8 +2,13 @@ Mini-Dokumentation: Analyzer Kiosk auf Raspberry Pi OS
|
||||
|
||||
1. Dienste deaktivieren
|
||||
sudo systemctl disable --now lightdm.service
|
||||
sudo systemctl disable --now getty@tty1.service
|
||||
sudo systemctl reset-failed getty@tty1.service
|
||||
sudo systemctl mask --now getty@tty1.service
|
||||
|
||||
Hinweis:
|
||||
- tty1 ist exklusiv fuer Xorg/Chromium reserviert. Ein blosses `disable` reicht
|
||||
nicht, weil systemd den Getty bei einem spaeteren Boot erneut erzeugen kann.
|
||||
- Fuer einen lokalen Login-Prompt auf tty1 den Kiosk zuerst stoppen und Getty
|
||||
anschliessend mit `sudo systemctl unmask --now getty@tty1.service` reaktivieren.
|
||||
|
||||
2. systemd neu laden
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
@@ -124,8 +124,25 @@ zugehörige sudo-Regel werden einmalig als root installiert:
|
||||
sudo bash /home/volumio/Phoenix/scripts/install_volumio_services.sh
|
||||
```
|
||||
|
||||
Der Installer verändert keine Debian-/Analyzer-Units und startet laufende
|
||||
Dienste nicht automatisch neu.
|
||||
Der Installer überschreibt keine Debian-/Analyzer-Unitdateien und startet
|
||||
laufende Dienste nicht automatisch neu. Er maskiert `getty@tty1.service`, da der
|
||||
Kiosk-X-Server `tty1` exklusiv verwendet. Ohne die Maskierung können Getty und
|
||||
Xorg beim Booten um dasselbe virtuelle Terminal konkurrieren und eine
|
||||
Kiosk-Neustartschleife auslösen.
|
||||
|
||||
Nach dieser einmaligen Grundinstallation bringt der privilegierte
|
||||
Volumio-Online-Updater bei jedem erfolgreichen Phoenix-Update auch die
|
||||
mitgelieferten systemd-Units, den Update-Helfer und die sudo-Regel aus. Fehlen
|
||||
diese Integrationsdateien in einem Update, wird es vor dem Austausch des
|
||||
laufenden Projektbaums abgelehnt.
|
||||
|
||||
Um den lokalen Login-Prompt auf `tty1` wiederherzustellen, zuerst den Kiosk
|
||||
stoppen und Getty anschließend wieder freigeben:
|
||||
|
||||
```bash
|
||||
sudo systemctl stop analyzer-kiosk-volumio.service
|
||||
sudo systemctl unmask --now getty@tty1.service
|
||||
```
|
||||
|
||||
Wenn `toggle-ext` genutzt werden soll, sollte es also unter diesem Pfad liegen:
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
[Unit]
|
||||
Description=Analyzer Kiosk Volumio (Raspberry Pi OS)
|
||||
Conflicts=getty@tty1.service
|
||||
After=systemd-user-sessions.service getty-pre.target
|
||||
After=network-online.target
|
||||
After=analyzer-web-volumio.service
|
||||
Wants=analyzer-web-volumio.service
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
[Unit]
|
||||
Description=Analyzer Kiosk (Raspberry Pi OS)
|
||||
Conflicts=getty@tty1.service
|
||||
After=systemd-user-sessions.service getty-pre.target
|
||||
After=network-online.target
|
||||
After=analyzer-web.service
|
||||
Wants=analyzer-web.service
|
||||
|
||||
@@ -51,8 +51,13 @@ install -o root -g root -m 0440 \
|
||||
"$SUDOERS_SOURCE" \
|
||||
/etc/sudoers.d/phoenix-update-volumio
|
||||
|
||||
# tty1 is reserved for Xorg by analyzer-kiosk-volumio.service. Disabling the
|
||||
# generated getty is insufficient because systemd may create/start it again on
|
||||
# a later boot. Mask it so the kiosk and agetty can never race for the VT.
|
||||
systemctl mask --now getty@tty1.service
|
||||
systemctl daemon-reload
|
||||
systemctl enable analyzer-web-volumio.service analyzer-kiosk-volumio.service
|
||||
|
||||
echo "Volumio integration installed"
|
||||
echo "getty@tty1.service masked; run 'systemctl unmask getty@tty1.service' to restore the tty1 login console"
|
||||
echo "Existing services were not restarted"
|
||||
@@ -209,6 +209,46 @@ build_volumio_candidate() {
|
||||
echo "staged Volumio build completed"
|
||||
}
|
||||
|
||||
validate_volumio_integration_candidate() {
|
||||
local phoenix_dir="${1:-$STAGE_DIR}"
|
||||
local required_path
|
||||
local required_paths=(
|
||||
"scripts/install_volumio_services.sh"
|
||||
"scripts/restart_phoenix_services.sh"
|
||||
"etc/systemd/system/analyzer-kiosk-volumio.service"
|
||||
"etc/systemd/system/analyzer-web-volumio.service"
|
||||
"etc/systemd/system/phoenix-update-volumio.service"
|
||||
"etc/sudoers.d/phoenix-update-volumio"
|
||||
)
|
||||
|
||||
for required_path in "${required_paths[@]}"; do
|
||||
if [ ! -f "$phoenix_dir/$required_path" ]; then
|
||||
echo "staged Volumio integration file missing: $phoenix_dir/$required_path" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
bash -n \
|
||||
"$phoenix_dir/scripts/install_volumio_services.sh" \
|
||||
"$phoenix_dir/scripts/restart_phoenix_services.sh"
|
||||
}
|
||||
|
||||
install_volumio_integration() {
|
||||
local phoenix_dir="${1:-$PHOENIX_ROOT}"
|
||||
local installer="$phoenix_dir/scripts/install_volumio_services.sh"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Volumio integration update requires root" >&2
|
||||
return 1
|
||||
fi
|
||||
if [ ! -f "$installer" ]; then
|
||||
echo "Volumio integration installer missing: $installer" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "installing Volumio system integration from $phoenix_dir"
|
||||
/bin/bash "$installer"
|
||||
}
|
||||
|
||||
start_all_services() {
|
||||
if is_volumio_install; then
|
||||
restart_if_present analyzer-web-volumio.service
|
||||
@@ -248,6 +288,12 @@ rollback_to_backup() {
|
||||
return 1
|
||||
fi
|
||||
mv "$BACKUP_DIR" "$PHOENIX_ROOT"
|
||||
if is_volumio_install && [ -f "$PHOENIX_ROOT/scripts/install_volumio_services.sh" ]; then
|
||||
if ! install_volumio_integration "$PHOENIX_ROOT"; then
|
||||
echo "backup files were restored but their Volumio integration could not be installed" >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if ! wait_for_candidate_health "$PHOENIX_ROOT" "${PHOENIX_ROLLBACK_HEALTH_ATTEMPTS:-120}" 1; then
|
||||
echo "backup rollback restored files but Phoenix health did not recover" >&2
|
||||
return 1
|
||||
@@ -289,6 +335,7 @@ if [ ! -f "$STAGE_DIR/Cargo.toml" ] || [ ! -d "$STAGE_DIR/src" ] || [ ! -d "$STA
|
||||
fi
|
||||
|
||||
if is_volumio_install; then
|
||||
validate_volumio_integration_candidate "$STAGE_DIR"
|
||||
build_volumio_candidate "$STAGE_DIR"
|
||||
fi
|
||||
|
||||
@@ -303,6 +350,17 @@ mv "$STAGE_DIR" "$PHOENIX_ROOT"
|
||||
health_attempts=45
|
||||
health_delay=1
|
||||
|
||||
if is_volumio_install; then
|
||||
if ! install_volumio_integration "$PHOENIX_ROOT"; then
|
||||
if ! rollback_to_backup; then
|
||||
echo "new Volumio integration failed and rollback failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "new Volumio integration failed; rolled back to backup" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! wait_for_candidate_health "$PHOENIX_ROOT" "$health_attempts" "$health_delay"; then
|
||||
if ! rollback_to_backup; then
|
||||
echo "new Phoenix version failed health check and rollback failed" >&2
|
||||
@@ -312,11 +370,4 @@ if ! wait_for_candidate_health "$PHOENIX_ROOT" "$health_attempts" "$health_delay
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if is_volumio_install && [ "$(id -u)" -eq 0 ] && [ -f "$PHOENIX_ROOT/scripts/restart_phoenix_services.sh" ]; then
|
||||
install -o root -g root -m 0755 \
|
||||
"$PHOENIX_ROOT/scripts/restart_phoenix_services.sh" \
|
||||
/usr/local/libexec/phoenix/restart_phoenix_services.sh
|
||||
echo "Volumio privileged update helper refreshed"
|
||||
fi
|
||||
|
||||
echo "=== $(date): restart_phoenix_services.sh OK ==="
|
||||
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
||||
|
||||
const html = fs.readFileSync(new URL('../www/index.html', import.meta.url), 'utf8');
|
||||
const main = fs.readFileSync(new URL('../www/main.js', import.meta.url), 'utf8');
|
||||
const options = fs.readFileSync(new URL('../www/ui/options.js', import.meta.url), 'utf8');
|
||||
const realtime = fs.readFileSync(new URL('../www/views/realtime.js', import.meta.url), 'utf8');
|
||||
|
||||
const styleSelect = html.match(/<select\b[^>]*\bid="styleSel"[^>]*>/)?.[0] || '';
|
||||
@@ -17,5 +18,9 @@ assert((realtime.match(/applyPeakHold\(state, integrated, CONFIG, range\)/g) ||
|
||||
'every RTA engine must advance peak hold on the display clock');
|
||||
assert(/useIirEngine[^]*?applyRealtimeBarBallistics\(state, displayBase, CONFIG, range\)/.test(realtime),
|
||||
'native IIR bars must advance their visible release on the display clock');
|
||||
assert(/h\('opt_rtaFreq', v => \{\s*CONFIG\.RTA_FREQ_RANGE = \(v === 'lf'\) \? 'lf' : 'norm'/.test(options),
|
||||
'the low-frequency range must remain selectable in the RTW layout');
|
||||
assert(!/RTA_FREQ_RANGE\s*=\s*CONFIG\.RTA_BAR_LAYOUT\s*===\s*'rtw'\s*\?\s*'norm'/.test(options),
|
||||
'the RTW layout must not force the normal frequency range');
|
||||
|
||||
console.log('GUI contract regression tests passed');
|
||||
@@ -31,9 +31,9 @@ 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);
|
||||
return new Array(31).fill(0);
|
||||
if (mode === '1_12') return new Array(145).fill(0);
|
||||
if (mode === '1_6') return new Array(73).fill(0);
|
||||
return new Array(37).fill(0);
|
||||
},
|
||||
});
|
||||
vm.runInContext(functionSource.replace('export function', 'function'), context);
|
||||
@@ -56,7 +56,7 @@ const forced = context.buildRtaRuntimeConfig({
|
||||
});
|
||||
assert.equal(forced.engine, 'iir');
|
||||
assert.equal(forced.bpo, '1_12');
|
||||
assert.equal(forced.freqRange, 'norm');
|
||||
assert.equal(forced.freqRange, 'lf');
|
||||
assert.equal(forced.order, 6);
|
||||
assert.equal(forced.detector, 'peak');
|
||||
assert.equal(forced.rtaPeakHoldMode, 'fall');
|
||||
@@ -64,7 +64,7 @@ assert.equal(forced.rtaPeakHoldSeconds, 4);
|
||||
assert.equal(forced.rtaPeakDecayDbPerSecond, 20);
|
||||
assert.equal(forced.rtaPeakResetToken, 9);
|
||||
assert.equal(forced.tauFast, 0.125);
|
||||
assert.equal(forced.rtwCenters.length, 121);
|
||||
assert.equal(forced.rtwCenters.length, 145);
|
||||
assert.equal(forced.correlationResponseS, 2.5);
|
||||
assert.equal(forced.correlationSilenceThresholdRmsDbfs, -50);
|
||||
assert.equal(forced.correlationResetToken, 7);
|
||||
@@ -99,6 +99,8 @@ assert.equal(selectionContext.applyRtaBpoSelection('1_3'), '1_3');
|
||||
assert.equal(selectionContext.CONFIG.RTA_BAR_LAYOUT, 'rtw');
|
||||
|
||||
const twelfthCenters = getRtwCenters('1_12');
|
||||
assert.equal(twelfthCenters[0], 5);
|
||||
assert.equal(twelfthCenters.at(-1), 20000);
|
||||
const staleTwelfthPacket = {
|
||||
engine: 'iir',
|
||||
bpo: '1_12',
|
||||
|
||||
+17
-15
@@ -90,24 +90,26 @@ const BOX_MAX_DB: f32 = 9.0;
|
||||
const NATIVE_RECORDER_DISCONTINUITY_BLEND_FRAMES: usize = 256;
|
||||
#[cfg(target_os = "linux")]
|
||||
const RTW_CENTERS_1_6: &[f32] = &[
|
||||
20.0, 22.4, 25.0, 28.0, 31.5, 35.5, 40.0, 45.0, 50.0, 56.0, 63.0, 70.8, 80.0, 90.0, 100.0,
|
||||
112.0, 125.0, 141.0, 160.0, 180.0, 200.0, 224.0, 250.0, 282.0, 315.0, 355.0, 400.0, 450.0,
|
||||
500.0, 560.0, 630.0, 708.0, 800.0, 900.0, 1000.0, 1120.0, 1250.0, 1410.0, 1600.0, 1800.0,
|
||||
2000.0, 2240.0, 2500.0, 2820.0, 3150.0, 3550.0, 4000.0, 4500.0, 5000.0, 5600.0, 6300.0, 7080.0,
|
||||
8000.0, 9000.0, 10000.0, 11200.0, 12500.0, 14100.0, 16000.0, 18000.0, 20000.0,
|
||||
5.0, 5.6, 6.3, 7.08, 8.0, 9.0, 10.0, 11.2, 12.5, 14.1, 16.0, 18.0, 20.0, 22.4, 25.0, 28.0,
|
||||
31.5, 35.5, 40.0, 45.0, 50.0, 56.0, 63.0, 70.8, 80.0, 90.0, 100.0, 112.0, 125.0, 141.0, 160.0,
|
||||
180.0, 200.0, 224.0, 250.0, 282.0, 315.0, 355.0, 400.0, 450.0, 500.0, 560.0, 630.0, 708.0,
|
||||
800.0, 900.0, 1000.0, 1120.0, 1250.0, 1410.0, 1600.0, 1800.0, 2000.0, 2240.0, 2500.0, 2820.0,
|
||||
3150.0, 3550.0, 4000.0, 4500.0, 5000.0, 5600.0, 6300.0, 7080.0, 8000.0, 9000.0, 10000.0,
|
||||
11200.0, 12500.0, 14100.0, 16000.0, 18000.0, 20000.0,
|
||||
];
|
||||
#[cfg(target_os = "linux")]
|
||||
const RTW_CENTERS_1_12: &[f32] = &[
|
||||
20.0, 21.2, 22.4, 23.8, 25.0, 26.5, 28.0, 29.8, 31.5, 33.4, 35.5, 37.6, 40.0, 42.4, 45.0, 47.5,
|
||||
50.0, 53.0, 56.0, 59.5, 63.0, 67.0, 71.0, 75.0, 80.0, 85.0, 90.0, 95.0, 100.0, 106.0, 112.0,
|
||||
118.0, 125.0, 132.0, 140.0, 150.0, 160.0, 170.0, 180.0, 190.0, 200.0, 212.0, 224.0, 238.0,
|
||||
250.0, 265.0, 280.0, 298.0, 315.0, 334.0, 355.0, 376.0, 400.0, 424.0, 450.0, 475.0, 500.0,
|
||||
530.0, 560.0, 595.0, 630.0, 670.0, 710.0, 750.0, 800.0, 850.0, 900.0, 950.0, 1000.0, 1060.0,
|
||||
1120.0, 1180.0, 1250.0, 1320.0, 1400.0, 1500.0, 1600.0, 1700.0, 1800.0, 1900.0, 2000.0, 2120.0,
|
||||
2240.0, 2380.0, 2500.0, 2650.0, 2800.0, 2980.0, 3150.0, 3340.0, 3550.0, 3760.0, 4000.0, 4240.0,
|
||||
4500.0, 4750.0, 5000.0, 5300.0, 5600.0, 5950.0, 6300.0, 6700.0, 7100.0, 7500.0, 8000.0, 8500.0,
|
||||
9000.0, 9500.0, 10_000.0, 10_600.0, 11_200.0, 11_800.0, 12_500.0, 13_200.0, 14_000.0, 15_000.0,
|
||||
16_000.0, 17_000.0, 18_000.0, 19_000.0, 20_000.0,
|
||||
5.0, 5.3, 5.6, 5.95, 6.3, 6.7, 7.1, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, 10.6, 11.2, 11.8, 12.5,
|
||||
13.2, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.2, 22.4, 23.8, 25.0, 26.5, 28.0, 29.8, 31.5,
|
||||
33.4, 35.5, 37.6, 40.0, 42.4, 45.0, 47.5, 50.0, 53.0, 56.0, 59.5, 63.0, 67.0, 71.0, 75.0, 80.0,
|
||||
85.0, 90.0, 95.0, 100.0, 106.0, 112.0, 118.0, 125.0, 132.0, 140.0, 150.0, 160.0, 170.0, 180.0,
|
||||
190.0, 200.0, 212.0, 224.0, 238.0, 250.0, 265.0, 280.0, 298.0, 315.0, 334.0, 355.0, 376.0,
|
||||
400.0, 424.0, 450.0, 475.0, 500.0, 530.0, 560.0, 595.0, 630.0, 670.0, 710.0, 750.0, 800.0,
|
||||
850.0, 900.0, 950.0, 1000.0, 1060.0, 1120.0, 1180.0, 1250.0, 1320.0, 1400.0, 1500.0, 1600.0,
|
||||
1700.0, 1800.0, 1900.0, 2000.0, 2120.0, 2240.0, 2380.0, 2500.0, 2650.0, 2800.0, 2980.0, 3150.0,
|
||||
3340.0, 3550.0, 3760.0, 4000.0, 4240.0, 4500.0, 4750.0, 5000.0, 5300.0, 5600.0, 5950.0, 6300.0,
|
||||
6700.0, 7100.0, 7500.0, 8000.0, 8500.0, 9000.0, 9500.0, 10_000.0, 10_600.0, 11_200.0, 11_800.0,
|
||||
12_500.0, 13_200.0, 14_000.0, 15_000.0, 16_000.0, 17_000.0, 18_000.0, 19_000.0, 20_000.0,
|
||||
];
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
+67
-8
@@ -17,6 +17,7 @@ pub struct CorrelationMeter {
|
||||
cross_power: f64,
|
||||
gate_power: f64,
|
||||
value: f32,
|
||||
display: f64,
|
||||
negative_peak: f32,
|
||||
reset_token: u64,
|
||||
}
|
||||
@@ -40,6 +41,7 @@ impl CorrelationMeter {
|
||||
cross_power: 0.0,
|
||||
gate_power: 0.0,
|
||||
value: 0.0,
|
||||
display: 0.0,
|
||||
negative_peak: 0.0,
|
||||
reset_token,
|
||||
};
|
||||
@@ -93,18 +95,24 @@ impl CorrelationMeter {
|
||||
* (0.5 * (l * l + r * r) - self.gate_power);
|
||||
|
||||
// Below the configured mono RMS threshold the correlation indication
|
||||
// settles at its neutral position.
|
||||
// eases back to its neutral position instead of snapping there.
|
||||
const MIN_POWER: f64 = 1.0e-10;
|
||||
let denominator = (self.power_l * self.power_r).sqrt();
|
||||
self.value = if self.gate_power >= self.silence_threshold_power
|
||||
let active = self.gate_power >= self.silence_threshold_power
|
||||
&& self.power_l > MIN_POWER
|
||||
&& self.power_r > MIN_POWER
|
||||
&& denominator > 0.0
|
||||
{
|
||||
(self.cross_power / denominator).clamp(-1.0, 1.0) as f32
|
||||
&& denominator > 0.0;
|
||||
if active {
|
||||
// Audio present: track the live correlation exactly as before.
|
||||
self.display = (self.cross_power / denominator).clamp(-1.0, 1.0);
|
||||
self.value = self.display as f32;
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
// Silent: hold the last value, then decay it back to zero using
|
||||
// the configured response time, so the XY cube settles instead of
|
||||
// jumping to center.
|
||||
self.display += self.alpha * (0.0 - self.display);
|
||||
self.value = self.display as f32;
|
||||
}
|
||||
if self.value < self.negative_peak {
|
||||
self.negative_peak = self.value;
|
||||
}
|
||||
@@ -255,7 +263,26 @@ mod tests {
|
||||
};
|
||||
(sample, sample)
|
||||
});
|
||||
assert_eq!(meter.value(), 0.0);
|
||||
// Raising the threshold above the signal level neutralizes the meter.
|
||||
// With the eased display it decays toward zero rather than snapping.
|
||||
assert!(
|
||||
meter.value() < 0.2,
|
||||
"meter did not neutralize: {}",
|
||||
meter.value()
|
||||
);
|
||||
run_signal(&mut meter, 3, |index| {
|
||||
let sample = if index & 1 == 0 {
|
||||
amplitude
|
||||
} else {
|
||||
-amplitude
|
||||
};
|
||||
(sample, sample)
|
||||
});
|
||||
assert!(
|
||||
meter.value().abs() < 0.05,
|
||||
"meter did not settle to zero: {}",
|
||||
meter.value()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -278,4 +305,36 @@ mod tests {
|
||||
});
|
||||
assert_eq!(meter.value(), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correlation_holds_then_decays_to_zero_on_silence() {
|
||||
let sample_rate = 48_000;
|
||||
let mut meter = CorrelationMeter::new(sample_rate, 1.0, -75.0, 0);
|
||||
// Drive a strongly correlated signal so the meter settles near +1.
|
||||
for index in 0..sample_rate as usize * 3 {
|
||||
let sample = if index & 1 == 0 { 0.5 } else { -0.5 };
|
||||
meter.process(sample, sample);
|
||||
}
|
||||
assert!(meter.value() > 0.9, "expected near +1, got {}", meter.value());
|
||||
|
||||
// Cut the audio: the value must hold (not snap to 0) right away...
|
||||
for _ in 0..10 {
|
||||
meter.process(0.0, 0.0);
|
||||
}
|
||||
assert!(
|
||||
meter.value() > 0.5,
|
||||
"value snapped to zero on silence: {}",
|
||||
meter.value()
|
||||
);
|
||||
|
||||
// ...and then decay back toward zero over the response time.
|
||||
for _ in 0..sample_rate as usize * 4 {
|
||||
meter.process(0.0, 0.0);
|
||||
}
|
||||
assert!(
|
||||
meter.value().abs() < 0.05,
|
||||
"value did not decay to zero: {}",
|
||||
meter.value()
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -6,9 +6,9 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
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,
|
||||
500.0, 630.0, 800.0, 1_000.0, 1_250.0, 1_600.0, 2_000.0, 2_500.0, 3_150.0, 4_000.0, 5_000.0,
|
||||
6_300.0, 8_000.0, 10_000.0, 12_500.0, 16_000.0, 20_000.0,
|
||||
5.0, 6.3, 8.0, 10.0, 12.5, 16.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, 500.0, 630.0, 800.0, 1_000.0, 1_250.0, 1_600.0, 2_000.0, 2_500.0,
|
||||
3_150.0, 4_000.0, 5_000.0, 6_300.0, 8_000.0, 10_000.0, 12_500.0, 16_000.0, 20_000.0,
|
||||
];
|
||||
|
||||
/// Maps a rounded preferred label to its exact IEC base-ten center frequency.
|
||||
@@ -448,7 +448,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn third_octave_butterworth_has_correct_center_and_edges() {
|
||||
assert_eq!(RTW_THIRD_OCTAVE_CENTERS.len(), 31);
|
||||
assert_eq!(RTW_THIRD_OCTAVE_CENTERS.len(), 37);
|
||||
for &nominal_center in RTW_THIRD_OCTAVE_CENTERS {
|
||||
let center = exact_fractional_octave_center(nominal_center, 3);
|
||||
let (lower, upper) = fractional_octave_edges(center, 3);
|
||||
|
||||
+1
-2
@@ -426,7 +426,6 @@ fn normalize_rta_config(mut config: PhoenixRtaConfig) -> PhoenixRtaConfig {
|
||||
if config.layout == "rtw" {
|
||||
config.engine = "iir".to_string();
|
||||
config.order = 6;
|
||||
config.freq_range = "norm".to_string();
|
||||
}
|
||||
|
||||
// Fast and Slow are named standard responses, not free tuning controls.
|
||||
@@ -853,7 +852,7 @@ mod tests {
|
||||
assert_eq!(config.filterbank, "legacy");
|
||||
assert_eq!(config.bpo, "1_12");
|
||||
assert_eq!(config.order, 6);
|
||||
assert_eq!(config.freq_range, "norm");
|
||||
assert_eq!(config.freq_range, "lf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+1
-1
@@ -927,7 +927,7 @@ export function buildRtaRuntimeConfig(CONFIG = {}) {
|
||||
lrFractionalDelayEnabled: !!CONFIG.LR_FRACTIONAL_DELAY_ENABLED,
|
||||
lrFractionalDelaySamples: Number.isFinite(CONFIG.LR_FRACTIONAL_DELAY_SAMPLES) ? CONFIG.LR_FRACTIONAL_DELAY_SAMPLES : 0,
|
||||
bpo: runtimeBpoMode,
|
||||
freqRange: rtwProfile ? 'norm' : (CONFIG.RTA_FREQ_RANGE || 'norm'),
|
||||
freqRange: CONFIG.RTA_FREQ_RANGE === 'lf' ? 'lf' : 'norm',
|
||||
weighting: CONFIG.RTA_WEIGHTING || 'z',
|
||||
order: rtwProfile ? 6 : (CONFIG.RTA_IIR_ORDER || 6),
|
||||
tauFast: CONFIG.RTA_IIR_TAU_FAST || 0.125,
|
||||
|
||||
@@ -821,7 +821,6 @@ function loadConfig(opts = {}) {
|
||||
if (CONFIG.RTA_BAR_LAYOUT === 'rtw') {
|
||||
CONFIG.RTA_ENGINE = 'iir';
|
||||
CONFIG.RTA_IIR_ORDER = 6;
|
||||
CONFIG.RTA_FREQ_RANGE = 'norm';
|
||||
const hold = Number(CONFIG.RTA_PEAK_HOLD_SEC);
|
||||
CONFIG.RTA_PEAK_HOLD_SEC = hold >= 3.25 ? 4.0 : 2.5;
|
||||
}
|
||||
@@ -1168,7 +1167,6 @@ function applyPhoenixGlobalConfig(payload = {}) {
|
||||
if (CONFIG.RTA_BAR_LAYOUT === 'rtw') {
|
||||
CONFIG.RTA_ENGINE = 'iir';
|
||||
CONFIG.RTA_IIR_ORDER = 6;
|
||||
CONFIG.RTA_FREQ_RANGE = 'norm';
|
||||
}
|
||||
const spectroGamma = Number(payload.spectroGamma);
|
||||
if (Number.isFinite(spectroGamma)) CONFIG.SPECTRO_GAMMA = Math.max(0.3, Math.min(1.2, spectroGamma));
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
export const RTW_CENTER_MAP = {
|
||||
'1_3': [
|
||||
20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160,
|
||||
5, 6.3, 8, 10, 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160,
|
||||
200, 250, 315, 400, 500, 630, 800, 1000, 1250, 1600,
|
||||
2000, 2500, 3150, 4000, 5000, 6300, 8000, 10000, 12500, 16000, 20000,
|
||||
],
|
||||
'1_6': [
|
||||
5, 5.6, 6.3, 7.08, 8, 9, 10, 11.2, 12.5, 14.1, 16, 18,
|
||||
20, 22.4, 25, 28, 31.5, 35.5, 40, 45, 50, 56,
|
||||
63, 70.8, 80, 90, 100, 112, 125, 141, 160, 180,
|
||||
200, 224, 250, 282, 315, 355, 400, 450, 500, 560,
|
||||
@@ -13,6 +14,8 @@ export const RTW_CENTER_MAP = {
|
||||
6300, 7080, 8000, 9000, 10000, 11200, 12500, 14100, 16000, 18000, 20000,
|
||||
],
|
||||
'1_12': [
|
||||
5, 5.3, 5.6, 5.95, 6.3, 6.7, 7.1, 7.5, 8, 8.5, 9, 9.5,
|
||||
10, 10.6, 11.2, 11.8, 12.5, 13.2, 14, 15, 16, 17, 18, 19,
|
||||
20, 21.2, 22.4, 23.8, 25, 26.5, 28, 29.8, 31.5, 33.4,
|
||||
35.5, 37.6, 40, 42.4, 45, 47.5, 50, 53, 56, 59.5,
|
||||
63, 67, 71, 75, 80, 85, 90, 95, 100, 106,
|
||||
|
||||
+1
-3
@@ -1002,7 +1002,7 @@ function wireHandlers(env) {
|
||||
return val;
|
||||
});
|
||||
h('opt_rtaFreq', v => {
|
||||
CONFIG.RTA_FREQ_RANGE = CONFIG.RTA_BAR_LAYOUT === 'rtw' ? 'norm' : ((v === 'lf') ? 'lf' : 'norm');
|
||||
CONFIG.RTA_FREQ_RANGE = (v === 'lf') ? 'lf' : 'norm';
|
||||
return CONFIG.RTA_FREQ_RANGE;
|
||||
});
|
||||
h('opt_rtaDisplayGainFft', v => { CONFIG.RTA_DISPLAY_GAIN_FFT_DB = clamp(+v, -25, 25); }, 'val_rtaDisplayGainFft', v=>`${v} dB`);
|
||||
@@ -1013,11 +1013,9 @@ function wireHandlers(env) {
|
||||
if (val === 'rtw') {
|
||||
CONFIG.RTA_ENGINE = 'iir';
|
||||
CONFIG.RTA_IIR_ORDER = 6;
|
||||
CONFIG.RTA_FREQ_RANGE = 'norm';
|
||||
const forced = {
|
||||
opt_rtaEngine: 'iir',
|
||||
opt_rtaOrder: '6',
|
||||
opt_rtaFreq: 'norm',
|
||||
};
|
||||
for (const [id, value] of Object.entries(forced)) {
|
||||
const control = document.getElementById(id);
|
||||
|
||||
@@ -11,7 +11,7 @@ const METER_PAD_BOTTOM = 5;
|
||||
const METER_SLOT_SHRINK = 24; // reduziert nutzbare Höhe leicht, damit Slot niedriger wirkt
|
||||
const METER_EXTRA_BOTTOM_PAD = 5;
|
||||
const FLOOR_DB = -120;
|
||||
const RTA_X_TICKS = [20, 31.5, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000];
|
||||
const RTA_X_TICKS = [5, 10, 20, 31.5, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000];
|
||||
const DEFAULT_RTA_BAR_BASE_COLOR = MID_COLOR;
|
||||
const RTA_PEAK_COLOR = WARN_COLOR;
|
||||
const EMBEDDED_RTA_HIDE_20HZ_WIDTH = 420;
|
||||
|
||||
Reference in new issue
Block a user