use std::env;
use std::error::Error;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use general_purpose_paracletic_hyper_cube::compression::{Codec, compress_with, decompress_with};
use general_purpose_paracletic_hyper_cube::cross_modal::{
byte_from_token, decode_from_hue, decode_from_note, encode_byte,
};
use general_purpose_paracletic_hyper_cube::media_samples::synth_y4m_musical_light;
const DEFAULT_FRAME_LIMIT: usize = 4096;
fn main() -> Result<(), Box<dyn Error>> {
let config = parse_args()?;
let (label, input) = match config.input_path {
Some(path) => {
let bytes = fs::read(&path)?;
let label = path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("input")
.to_string();
(label, bytes)
}
None => (
"generated-video-y4m".to_string(),
synth_y4m_musical_light(192, 108, 75, 25),
),
};
let compressed = compress_with(Codec::Chromoharmonic, &input);
let decompressed = decompress_with(Codec::Chromoharmonic, &compressed)?;
let storage_roundtrip_ok = decompressed == input;
let frames_to_map = compressed.len().min(config.frame_limit);
let mapped_slice = &compressed[..frames_to_map];
let mut note_recovered = Vec::with_capacity(mapped_slice.len());
let mut hue_recovered = Vec::with_capacity(mapped_slice.len());
let mut token_recovered = Vec::with_capacity(mapped_slice.len());
let out_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target/cross_modal_demo");
fs::create_dir_all(&out_dir)?;
let csv_path = out_dir.join("frames.csv");
let score_path = out_dir.join("score.txt");
let ppm_path = out_dir.join("palette.ppm");
let source_path = out_dir.join("source.bin");
let compressed_path = out_dir.join("encoded.chromoharmonic.bin");
let restored_path = out_dir.join("restored.bin");
let mut csv = fs::File::create(&csv_path)?;
writeln!(csv, "index,byte,note,register,hue_index,r,g,b,token")?;
let mut score = fs::File::create(&score_path)?;
let mut colors = Vec::with_capacity(mapped_slice.len());
for (i, &b) in mapped_slice.iter().enumerate() {
let frame = encode_byte(b);
note_recovered.push(decode_from_note(frame.note_name, frame.register)?);
hue_recovered.push(decode_from_hue(frame.hue_index, frame.register)?);
token_recovered.push(byte_from_token(&frame.token)?);
colors.push(frame.rgb);
let hex = format!(
"#{:02X}{:02X}{:02X}",
frame.rgb[0], frame.rgb[1], frame.rgb[2]
);
writeln!(
csv,
"{},{},{},{},{},{},{},{},{}",
i,
frame.byte,
frame.note_name,
frame.register,
frame.hue_index,
frame.rgb[0],
frame.rgb[1],
frame.rgb[2],
frame.token
)?;
writeln!(
score,
"{:06} {:>3}{} {:<11} {}",
i, frame.note_name, frame.register, frame.token, hex
)?;
}
write_palette_ppm(&ppm_path, &colors)?;
fs::write(&source_path, &input)?;
fs::write(&compressed_path, &compressed)?;
fs::write(&restored_path, &decompressed)?;
let note_ok = note_recovered == mapped_slice;
let hue_ok = hue_recovered == mapped_slice;
let token_ok = token_recovered == mapped_slice;
println!("Cross-modal PHC demonstration");
println!("dataset: {}", label);
println!("input bytes: {}", input.len());
println!("chromoharmonic bytes: {}", compressed.len());
println!(
"compression ratio: {:.3}",
compressed.len() as f64 / input.len().max(1) as f64
);
println!("storage roundtrip: {}", yes_no(storage_roundtrip_ok));
println!(
"mapped frames: {} (first {} compressed bytes)",
frames_to_map, frames_to_map
);
println!("music decode roundtrip: {}", yes_no(note_ok));
println!("light decode roundtrip: {}", yes_no(hue_ok));
println!("word decode roundtrip: {}", yes_no(token_ok));
println!();
println!("Artifacts:");
println!(" {}", csv_path.display());
println!(" {}", score_path.display());
println!(" {}", ppm_path.display());
println!(" {}", source_path.display());
println!(" {}", compressed_path.display());
println!(" {}", restored_path.display());
if !(storage_roundtrip_ok && note_ok && hue_ok && token_ok) {
return Err(io::Error::other("cross-modal verification failed").into());
}
Ok(())
}
fn parse_args() -> Result<Config, Box<dyn Error>> {
let mut args = env::args().skip(1);
let mut input_path: Option<PathBuf> = None;
let mut frame_limit = DEFAULT_FRAME_LIMIT;
while let Some(arg) = args.next() {
if arg == "--input" {
let value = args.next().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "--input requires a path")
})?;
input_path = Some(PathBuf::from(value));
continue;
}
if let Some(v) = arg.strip_prefix("--input=") {
input_path = Some(PathBuf::from(v));
continue;
}
if arg == "--frames" {
let value = args.next().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "--frames requires a number")
})?;
frame_limit = value.parse::<usize>()?.max(1);
continue;
}
if let Some(v) = arg.strip_prefix("--frames=") {
frame_limit = v.parse::<usize>()?.max(1);
continue;
}
return Err(
io::Error::new(io::ErrorKind::InvalidInput, format!("unknown arg: {arg}")).into(),
);
}
Ok(Config {
input_path,
frame_limit,
})
}
fn write_palette_ppm(path: &Path, colors: &[[u8; 3]]) -> Result<(), Box<dyn Error>> {
let width = colors.len().max(1);
let height = 64usize;
let mut bytes = Vec::with_capacity(width * height * 3 + 64);
bytes.extend_from_slice(format!("P6\n{} {}\n255\n", width, height).as_bytes());
for _y in 0..height {
for x in 0..width {
let rgb = colors.get(x).copied().unwrap_or([0, 0, 0]);
bytes.extend_from_slice(&rgb);
}
}
fs::write(path, bytes)?;
Ok(())
}
fn yes_no(v: bool) -> &'static str {
if v { "yes" } else { "no" }
}
struct Config {
input_path: Option<PathBuf>,
frame_limit: usize,
}