Skip to main content

paracletics_hypercube/compression/
mod.rs

1mod chromoharmonic;
2mod delta_pulse;
3mod rle;
4
5use std::error::Error;
6use std::fmt;
7
8use chromoharmonic::Chromoharmonic;
9use delta_pulse::DeltaPulse;
10use rle::RunLength;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum Codec {
14    Chromoharmonic,
15    DeltaPulse,
16    RunLength,
17}
18
19impl Codec {
20    pub const ALL: [Codec; 3] = [Codec::Chromoharmonic, Codec::DeltaPulse, Codec::RunLength];
21
22    pub fn name(self) -> &'static str {
23        match self {
24            Codec::Chromoharmonic => "chromoharmonic",
25            Codec::DeltaPulse => "delta-pulse",
26            Codec::RunLength => "run-length",
27        }
28    }
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub enum CompressionError {
33    CorruptStream(&'static str),
34    ValueOverflow,
35}
36
37impl fmt::Display for CompressionError {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        match self {
40            CompressionError::CorruptStream(msg) => write!(f, "corrupt stream: {msg}"),
41            CompressionError::ValueOverflow => {
42                write!(f, "decoded value overflowed byte range")
43            }
44        }
45    }
46}
47
48impl Error for CompressionError {}
49
50#[derive(Clone, Copy, Debug, PartialEq)]
51pub struct CompressionStats {
52    pub input_bytes: usize,
53    pub output_bytes: usize,
54}
55
56impl CompressionStats {
57    pub fn ratio(self) -> f64 {
58        if self.input_bytes == 0 {
59            return 1.0;
60        }
61        self.output_bytes as f64 / self.input_bytes as f64
62    }
63}
64
65pub trait Compressor: Sync {
66    fn name(&self) -> &'static str;
67    fn compress(&self, input: &[u8]) -> Vec<u8>;
68    fn decompress(&self, input: &[u8]) -> Result<Vec<u8>, CompressionError>;
69    fn stats(&self, input: &[u8]) -> Result<CompressionStats, CompressionError> {
70        let compressed = self.compress(input);
71        let decoded = self.decompress(&compressed)?;
72        if decoded != input {
73            return Err(CompressionError::CorruptStream(
74                "self-check roundtrip failed",
75            ));
76        }
77        Ok(CompressionStats {
78            input_bytes: input.len(),
79            output_bytes: compressed.len(),
80        })
81    }
82}
83
84static CHROMOHARMONIC: Chromoharmonic = Chromoharmonic;
85static DELTA_PULSE: DeltaPulse = DeltaPulse;
86static RUN_LENGTH: RunLength = RunLength;
87
88pub fn compressor_for(codec: Codec) -> &'static dyn Compressor {
89    match codec {
90        Codec::Chromoharmonic => &CHROMOHARMONIC,
91        Codec::DeltaPulse => &DELTA_PULSE,
92        Codec::RunLength => &RUN_LENGTH,
93    }
94}
95
96pub fn compress_with(codec: Codec, input: &[u8]) -> Vec<u8> {
97    compressor_for(codec).compress(input)
98}
99
100pub fn decompress_with(codec: Codec, input: &[u8]) -> Result<Vec<u8>, CompressionError> {
101    compressor_for(codec).decompress(input)
102}
103
104pub fn stats_with(codec: Codec, input: &[u8]) -> Result<CompressionStats, CompressionError> {
105    compressor_for(codec).stats(input)
106}