#![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 crate::test_tone::{lum_to_freq, ToneWriter, PORCH_HZ, SEPTR_HZ, SYNC_HZ};
#[must_use]
#[allow(dead_code, clippy::too_many_lines)]
pub(crate) fn encode_scottie(mode: SstvMode, rgb: &[[u8; 3]]) -> Vec<f32> {
assert!(matches!(
mode,
SstvMode::Scottie1
| SstvMode::Scottie2
| SstvMode::ScottieDx
| SstvMode::Martin1
| SstvMode::Martin2
));
let spec = crate::modespec::for_mode(mode);
let w = spec.line_pixels;
let h = spec.image_lines;
assert_eq!(rgb.len() as u32, w * h);
let sr = f64::from(WORKING_SAMPLE_RATE_HZ);
let mut tone = ToneWriter::new();
let mut t = 0.0_f64;
let advance = |t: &mut f64, secs: f64| -> usize {
*t += secs;
(*t * sr).round() as usize
};
for y in 0..h {
match spec.sync_position {
crate::modespec::SyncPosition::Scottie => {
tone.fill_to(SEPTR_HZ, advance(&mut t, spec.septr_seconds));
for x in 0..w {
let g = rgb[(y * w + x) as usize][1];
tone.fill_to(lum_to_freq(g), advance(&mut t, spec.pixel_seconds));
}
tone.fill_to(SEPTR_HZ, advance(&mut t, spec.septr_seconds));
for x in 0..w {
let b = rgb[(y * w + x) as usize][2];
tone.fill_to(lum_to_freq(b), advance(&mut t, spec.pixel_seconds));
}
tone.fill_to(SYNC_HZ, advance(&mut t, spec.sync_seconds));
tone.fill_to(PORCH_HZ, advance(&mut t, spec.porch_seconds));
for x in 0..w {
let r = rgb[(y * w + x) as usize][0];
tone.fill_to(lum_to_freq(r), advance(&mut t, spec.pixel_seconds));
}
}
crate::modespec::SyncPosition::LineStart => {
tone.fill_to(SYNC_HZ, advance(&mut t, spec.sync_seconds));
tone.fill_to(PORCH_HZ, advance(&mut t, spec.porch_seconds));
for x in 0..w {
let g = rgb[(y * w + x) as usize][1];
tone.fill_to(lum_to_freq(g), advance(&mut t, spec.pixel_seconds));
}
tone.fill_to(SEPTR_HZ, advance(&mut t, spec.septr_seconds));
for x in 0..w {
let b = rgb[(y * w + x) as usize][2];
tone.fill_to(lum_to_freq(b), advance(&mut t, spec.pixel_seconds));
}
tone.fill_to(SEPTR_HZ, advance(&mut t, spec.septr_seconds));
for x in 0..w {
let r = rgb[(y * w + x) as usize][0];
tone.fill_to(lum_to_freq(r), advance(&mut t, spec.pixel_seconds));
}
}
}
let line_end_target = f64::from(y + 1) * spec.line_seconds;
let pad_secs = line_end_target - t;
if pad_secs > 0.0 {
tone.fill_to(PORCH_HZ, advance(&mut t, pad_secs));
}
}
tone.into_vec()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scottie1_encode_total_length() {
let rgb = vec![[128u8; 3]; 320 * 256];
let audio = encode_scottie(SstvMode::Scottie1, &rgb);
let spec = crate::modespec::for_mode(SstvMode::Scottie1);
let expected_len = (spec.line_seconds
* f64::from(spec.image_lines)
* f64::from(WORKING_SAMPLE_RATE_HZ)) as usize;
assert!(audio.len() >= expected_len);
assert!(audio.len() <= expected_len + 1);
}
}