pluto_sdr/
pam.rs

1// Author: Roman Hayn
2// MIT License 2023
3
4use std::string::FromUtf8Error;
5
6/// Providing an interface to transcode e.g. Strings (i.e. UTF-8) into Pulse-Amplitude-Modulation
7trait Pam {
8    fn to_pam(&self) -> Vec<u8>;
9    fn from_pam(pam: Vec<u8>) -> Result<String, FromUtf8Error>;
10}
11
12impl Pam for String {
13    /// convert String to UTF-8, and its bytes to PAM Symbols
14    /// 4PAM gives 4 values per byte, since each 4PAM valua can have 4 values (0,1,2,3)
15    fn to_pam(&self) -> Vec<u8> {
16        let bytes = self.as_bytes();
17        let mut result_vec: Vec<u8> = Vec::with_capacity(bytes.len() * 4);
18
19        for byte in bytes {
20            println!("0x{:08b}", byte);
21            for i in 0..=3 {
22                let downshifted = byte >> (3 - i) * 2;
23                let masked = downshifted & 0b00000011;
24                result_vec.push(masked);
25            }
26        }
27
28        println!("vec: {:?}", result_vec);
29        return result_vec;
30    }
31
32    fn from_pam(pam: Vec<u8>) -> Result<String, FromUtf8Error> {
33        // len is the amount of utf- symbols (not string length!)
34        let len = pam.len() / 4;
35        // we over 1 byte per utf-8 symbol/code point
36        let mut bytes: Vec<u8> = Vec::with_capacity(len);
37
38        for i in 0..len {
39            // reassemble the byte from 4x pam samples
40            let mut byte: u8 = 0b00000000;
41            for j in 0..=3 {
42                byte |= pam[i * 4 + j] << 2 * (3 - j);
43            }
44            bytes.push(byte);
45        }
46        return String::from_utf8(bytes);
47    }
48}
49
50#[cfg(test)]
51mod test {
52    use super::*;
53
54    #[test]
55    fn test_utf_8_size() {
56        // € is 3 bytes long in utf-8
57        let s: String = "hello €".to_string();
58        assert_eq!(s.len(), 9);
59    }
60
61    #[test]
62    fn string_to_pam() {
63        let s: String = "not a planet".to_string();
64        let pam = s.to_pam();
65
66        let correct_pam = [
67            1, 2, 3, 2, 1, 2, 3, 3, 1, 3, 1, 0, 0, 2, 0, 0, 1, 2, 0, 1, 0, 2, 0, 0, 1, 3, 0, 0, 1,
68            2, 3, 0, 1, 2, 0, 1, 1, 2, 3, 2, 1, 2, 1, 1, 1, 3, 1, 0,
69        ];
70        assert_eq!(pam, correct_pam);
71    }
72
73    #[test]
74    fn pam_to_string() {
75        let pam = vec![
76            1, 2, 3, 3, 1, 3, 0, 2, 0, 2, 0, 0, 1, 2, 2, 1, 1, 3, 0, 3, 0, 2, 0, 0, 1, 2, 2, 1, 1,
77            3, 1, 0, 0, 3, 3, 3,
78        ];
79        let s = String::from_pam(pam);
80        let correct_s = "or is it?".to_string();
81        assert_eq!(s.unwrap(), correct_s);
82    }
83
84    #[test]
85    fn str_to_pam_to_str() {
86        // no matter what symbols we throw at it, if they are properly escaped (not {:?}), this
87        // should never fail
88        let s = "The quick brown fox jumps over the lazy dog 0123456789!@#$%^&*()€\n\r".to_string();
89        let p = s.to_pam();
90
91        let reconst = String::from_pam(p).unwrap();
92        assert_eq!(s, reconst);
93    }
94}