#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
use crate::modespec::SstvMode;
use crate::resample::WORKING_SAMPLE_RATE_HZ;
use std::f64::consts::PI;
const SYNC_HZ: f64 = 1200.0;
const PORCH_HZ: f64 = 1500.0;
const BLACK_HZ: f64 = 1500.0;
const WHITE_HZ: f64 = 2300.0;
fn lum_to_freq(lum: u8) -> f64 {
BLACK_HZ + (WHITE_HZ - BLACK_HZ) * f64::from(lum) / 255.0
}
fn fill_to(out: &mut Vec<f32>, freq_hz: f64, target_n: usize, phase: &mut f64) {
let dphi = 2.0 * PI * freq_hz / f64::from(WORKING_SAMPLE_RATE_HZ);
while out.len() < target_n {
out.push(phase.sin() as f32);
*phase += dphi;
if *phase > 2.0 * PI {
*phase -= 2.0 * PI;
}
}
}
#[must_use]
#[doc(hidden)]
#[allow(dead_code)]
pub fn encode_robot(mode: SstvMode, ycrcb: &[[u8; 3]]) -> Vec<f32> {
assert!(matches!(
mode,
SstvMode::Robot24 | SstvMode::Robot36 | SstvMode::Robot72
));
let spec = crate::modespec::for_mode(mode);
let w = spec.line_pixels;
let h = spec.image_lines;
assert_eq!(ycrcb.len() as u32, w * h);
let sr = f64::from(WORKING_SAMPLE_RATE_HZ);
let mut out = Vec::new();
let mut phase = 0.0_f64;
let mut t = 0.0_f64;
let advance = |t: &mut f64, secs: f64| -> usize {
*t += secs;
(*t * sr).round() as usize
};
match mode {
SstvMode::Robot72 => encode_r72(&mut out, &mut phase, &mut t, advance, &spec, ycrcb),
SstvMode::Robot24 | SstvMode::Robot36 => {
encode_r36_or_r24(&mut out, &mut phase, &mut t, advance, &spec, ycrcb);
}
_ => unreachable!(),
}
out
}
#[allow(clippy::too_many_arguments)]
fn encode_r72(
out: &mut Vec<f32>,
phase: &mut f64,
t: &mut f64,
mut advance: impl FnMut(&mut f64, f64) -> usize,
spec: &crate::modespec::ModeSpec,
ycrcb: &[[u8; 3]],
) {
let w = spec.line_pixels;
let h = spec.image_lines;
for y in 0..h {
fill_to(out, SYNC_HZ, advance(t, spec.sync_seconds), phase);
fill_to(out, PORCH_HZ, advance(t, spec.porch_seconds), phase);
for x in 0..w {
let lum = ycrcb[(y * w + x) as usize][0];
fill_to(out, lum_to_freq(lum), advance(t, spec.pixel_seconds), phase);
}
fill_to(out, PORCH_HZ, advance(t, spec.septr_seconds), phase);
for x in 0..w {
let cr = ycrcb[(y * w + x) as usize][1];
fill_to(out, lum_to_freq(cr), advance(t, spec.pixel_seconds), phase);
}
fill_to(out, PORCH_HZ, advance(t, spec.septr_seconds), phase);
for x in 0..w {
let cb = ycrcb[(y * w + x) as usize][2];
fill_to(out, lum_to_freq(cb), advance(t, spec.pixel_seconds), phase);
}
let line_end_target = f64::from(y + 1) * spec.line_seconds;
let pad_secs = line_end_target - *t;
if pad_secs > 0.0 {
fill_to(out, PORCH_HZ, advance(t, pad_secs), phase);
}
}
}
#[allow(clippy::too_many_arguments)]
fn encode_r36_or_r24(
out: &mut Vec<f32>,
phase: &mut f64,
t: &mut f64,
mut advance: impl FnMut(&mut f64, f64) -> usize,
spec: &crate::modespec::ModeSpec,
ycrcb: &[[u8; 3]],
) {
let w = spec.line_pixels;
let h = spec.image_lines;
for y in 0..h {
fill_to(out, SYNC_HZ, advance(t, spec.sync_seconds), phase);
fill_to(out, PORCH_HZ, advance(t, spec.porch_seconds), phase);
for x in 0..w {
let lum = ycrcb[(y * w + x) as usize][0];
fill_to(
out,
lum_to_freq(lum),
advance(t, spec.pixel_seconds * 2.0),
phase,
);
}
fill_to(out, PORCH_HZ, advance(t, spec.septr_seconds), phase);
let chroma_idx = if y % 2 == 0 { 1_usize } else { 2_usize };
for x in 0..w {
let chroma = ycrcb[(y * w + x) as usize][chroma_idx];
fill_to(
out,
lum_to_freq(chroma),
advance(t, spec.pixel_seconds),
phase,
);
}
}
}