oximedia_codec/vorbis/
codebook.rs1#![forbid(unsafe_code)]
8#![allow(clippy::cast_possible_truncation)]
9
10#[derive(Clone, Debug)]
12pub struct CodebookEntry {
13 pub length: u8,
15 pub value: Vec<f32>,
17}
18
19#[derive(Clone, Debug)]
21pub struct Codebook {
22 pub entries: usize,
24 pub table: Vec<CodebookEntry>,
26 pub dimensions: usize,
28}
29
30impl Codebook {
31 #[must_use]
36 pub fn from_lengths_and_values(lengths: &[u8], values: &[f32]) -> Self {
37 let entries = lengths.len();
38 let table = (0..entries)
39 .map(|i| CodebookEntry {
40 length: lengths[i],
41 value: vec![values[i.min(values.len() - 1)]],
42 })
43 .collect();
44 Self {
45 entries,
46 table,
47 dimensions: 1,
48 }
49 }
50
51 #[must_use]
55 pub fn lookup(&self, index: usize) -> Option<&[f32]> {
56 self.table
57 .get(index)
58 .filter(|e| e.length > 0)
59 .map(|e| e.value.as_slice())
60 }
61
62 #[must_use]
64 pub fn average_code_length(&self) -> f64 {
65 let active: Vec<u8> = self
66 .table
67 .iter()
68 .filter(|e| e.length > 0)
69 .map(|e| e.length)
70 .collect();
71 if active.is_empty() {
72 return 0.0;
73 }
74 active.iter().map(|&l| f64::from(l)).sum::<f64>() / active.len() as f64
75 }
76
77 #[must_use]
79 pub fn kraft_inequality_satisfied(&self) -> bool {
80 let sum: f64 = self
81 .table
82 .iter()
83 .filter(|e| e.length > 0)
84 .map(|e| 2.0f64.powi(-(e.length as i32)))
85 .sum();
86 sum <= 1.0 + 1e-9
87 }
88}
89
90#[cfg(test)]
95mod tests {
96 use super::*;
97
98 fn simple_book() -> Codebook {
99 let lengths = [1u8, 2, 3, 3];
100 let values = [0.0f32, 1.0, 2.0, 3.0];
101 Codebook::from_lengths_and_values(&lengths, &values)
102 }
103
104 #[test]
105 fn test_codebook_entry_count() {
106 let book = simple_book();
107 assert_eq!(book.entries, 4);
108 }
109
110 #[test]
111 fn test_codebook_lookup_valid() {
112 let book = simple_book();
113 let v = book.lookup(0).expect("entry 0 should exist");
114 assert!((v[0] - 0.0).abs() < 1e-6);
115 }
116
117 #[test]
118 fn test_codebook_lookup_out_of_range() {
119 let book = simple_book();
120 assert!(book.lookup(10).is_none());
121 }
122
123 #[test]
124 fn test_codebook_average_length() {
125 let book = simple_book();
126 let avg = book.average_code_length();
127 assert!((avg - 2.25).abs() < 1e-9, "Expected 2.25, got {avg}");
129 }
130
131 #[test]
132 fn test_codebook_kraft_inequality() {
133 let book = simple_book();
134 assert!(book.kraft_inequality_satisfied());
136 }
137
138 #[test]
139 fn test_codebook_unused_entry_length_zero() {
140 let lengths = [0u8, 2, 0, 3];
141 let values = [0.0f32; 4];
142 let book = Codebook::from_lengths_and_values(&lengths, &values);
143 assert!(book.lookup(0).is_none()); assert!(book.lookup(1).is_some());
145 assert!(book.lookup(2).is_none());
146 }
147
148 #[test]
149 fn test_codebook_dimension_is_one() {
150 let book = simple_book();
151 assert_eq!(book.dimensions, 1);
152 }
153}