189 lines
4.1 KiB
Rust
189 lines
4.1 KiB
Rust
//! Shared A/V signature palette for synthetic sync probes.
|
|
//!
|
|
//! The analyzer and generator both need the same small codebook. Keeping the
|
|
//! colors and tones here prevents a subtle class of transport-test regressions:
|
|
//! a client could emit a valid-looking pulse that the downstream analyzer no
|
|
//! longer recognizes as the same event identity.
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub(crate) struct ProbeColor {
|
|
pub r: u8,
|
|
pub g: u8,
|
|
pub b: u8,
|
|
}
|
|
|
|
pub(crate) const MAX_EVENT_CODE: u32 = 16;
|
|
|
|
const PROBE_AUDIO_TONE_FREQUENCIES_HZ: [f64; MAX_EVENT_CODE as usize] = [
|
|
620.0, 780.0, 940.0, 1120.0, 1320.0, 1540.0, 1780.0, 2040.0, 2320.0, 2620.0, 2960.0, 3340.0,
|
|
3760.0, 4220.0, 4740.0, 5320.0,
|
|
];
|
|
|
|
const PROBE_COLOR_PALETTE: [(u32, ProbeColor); MAX_EVENT_CODE as usize] = [
|
|
(
|
|
1,
|
|
ProbeColor {
|
|
r: 255,
|
|
g: 45,
|
|
b: 45,
|
|
},
|
|
),
|
|
(
|
|
2,
|
|
ProbeColor {
|
|
r: 0,
|
|
g: 230,
|
|
b: 118,
|
|
},
|
|
),
|
|
(
|
|
3,
|
|
ProbeColor {
|
|
r: 41,
|
|
g: 121,
|
|
b: 255,
|
|
},
|
|
),
|
|
(
|
|
4,
|
|
ProbeColor {
|
|
r: 255,
|
|
g: 179,
|
|
b: 0,
|
|
},
|
|
),
|
|
(
|
|
5,
|
|
ProbeColor {
|
|
r: 216,
|
|
g: 27,
|
|
b: 96,
|
|
},
|
|
),
|
|
(
|
|
6,
|
|
ProbeColor {
|
|
r: 0,
|
|
g: 188,
|
|
b: 212,
|
|
},
|
|
),
|
|
(
|
|
7,
|
|
ProbeColor {
|
|
r: 205,
|
|
g: 220,
|
|
b: 57,
|
|
},
|
|
),
|
|
(
|
|
8,
|
|
ProbeColor {
|
|
r: 126,
|
|
g: 87,
|
|
b: 194,
|
|
},
|
|
),
|
|
(
|
|
9,
|
|
ProbeColor {
|
|
r: 255,
|
|
g: 112,
|
|
b: 67,
|
|
},
|
|
),
|
|
(
|
|
10,
|
|
ProbeColor {
|
|
r: 38,
|
|
g: 166,
|
|
b: 154,
|
|
},
|
|
),
|
|
(
|
|
11,
|
|
ProbeColor {
|
|
r: 255,
|
|
g: 64,
|
|
b: 129,
|
|
},
|
|
),
|
|
(
|
|
12,
|
|
ProbeColor {
|
|
r: 92,
|
|
g: 107,
|
|
b: 192,
|
|
},
|
|
),
|
|
(
|
|
13,
|
|
ProbeColor {
|
|
r: 255,
|
|
g: 235,
|
|
b: 59,
|
|
},
|
|
),
|
|
(
|
|
14,
|
|
ProbeColor {
|
|
r: 105,
|
|
g: 240,
|
|
b: 174,
|
|
},
|
|
),
|
|
(
|
|
15,
|
|
ProbeColor {
|
|
r: 171,
|
|
g: 71,
|
|
b: 188,
|
|
},
|
|
),
|
|
(
|
|
16,
|
|
ProbeColor {
|
|
r: 3,
|
|
g: 169,
|
|
b: 244,
|
|
},
|
|
),
|
|
];
|
|
|
|
/// Return the audio frequency assigned to a probe event code.
|
|
///
|
|
/// Inputs: one-based event code.
|
|
/// Outputs: tone frequency in hertz, or `None` for unsupported codes.
|
|
/// Why: coded client-origin probes must preserve event identity after network
|
|
/// transport, not just pulse cadence.
|
|
pub(crate) fn probe_audio_frequency_for_event_code(code: u32) -> Option<f64> {
|
|
PROBE_AUDIO_TONE_FREQUENCIES_HZ
|
|
.get(code.checked_sub(1)? as usize)
|
|
.copied()
|
|
}
|
|
|
|
/// Return the video color assigned to a probe event code.
|
|
///
|
|
/// Inputs: one-based event code.
|
|
/// Outputs: saturated RGB color, or `None` for unsupported codes.
|
|
/// Why: final RCT captures can drop early frames; color identity lets the
|
|
/// analyzer rejoin observed flashes with the exact client-generated event.
|
|
pub(crate) fn probe_color_for_event_code(code: u32) -> Option<ProbeColor> {
|
|
PROBE_COLOR_PALETTE
|
|
.into_iter()
|
|
.find_map(|(palette_code, color)| (palette_code == code).then_some(color))
|
|
}
|
|
|
|
/// Validate that every event code can be rendered and analyzed.
|
|
///
|
|
/// Inputs: user-supplied code sequence.
|
|
/// Outputs: unsupported code if one is present.
|
|
/// Why: failing at CLI/config time produces clearer diagnostics than a later
|
|
/// analyzer report with missing pulses.
|
|
pub(crate) fn first_unsupported_event_code(codes: &[u32]) -> Option<u32> {
|
|
codes.iter().copied().find(|code| {
|
|
probe_audio_frequency_for_event_code(*code).is_none()
|
|
|| probe_color_for_event_code(*code).is_none()
|
|
})
|
|
}
|