flacenc/
lib.rs

1// Copyright 2022-2024 Google LLC
2// Copyright 2025- flacenc-rs developers
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![doc = include_str!("../README.md")]
17#![cfg_attr(feature = "simd-nightly", feature(portable_simd))]
18#![cfg_attr(feature = "simd-nightly", feature(test))]
19// Note that clippy attributes should be in sync with those declared in "main.rs"
20#![warn(clippy::all, clippy::nursery, clippy::pedantic, clippy::cargo)]
21#![allow(unknown_lints)]
22// Some of clippy::pedantic rules are actually useful, so use it with a lot of
23// ad-hoc exceptions.
24#![allow(
25    clippy::cast_possible_truncation,
26    clippy::cast_possible_wrap,
27    clippy::cast_precision_loss,
28    clippy::cast_sign_loss,
29    clippy::missing_const_for_fn,
30    clippy::multiple_crate_versions,
31    clippy::must_use_candidate,
32    clippy::wildcard_dependencies,
33    clippy::manual_is_multiple_of // `is_multiple_of` is introduced at 1.87.
34)]
35// Some from restriction lint-group
36#![warn(
37    clippy::clone_on_ref_ptr,
38    clippy::create_dir,
39    clippy::dbg_macro,
40    clippy::empty_structs_with_brackets,
41    clippy::exit,
42    clippy::if_then_some_else_none,
43    clippy::impl_trait_in_params,
44    clippy::implicit_clone,
45    clippy::let_underscore_must_use,
46    clippy::lossy_float_literal,
47    clippy::multiple_inherent_impl,
48    clippy::print_stdout,
49    clippy::rc_buffer,
50    clippy::rc_mutex,
51    clippy::rest_pat_in_fully_bound_structs,
52    clippy::separated_literal_suffix,
53    clippy::str_to_string,
54    clippy::string_add,
55    clippy::try_err,
56    clippy::unnecessary_self_imports,
57    clippy::wildcard_enum_match_arm
58)]
59// Because `built` crate generates `built.rs` with clippy errors.
60#![allow(clippy::needless_raw_string_hashes, clippy::doc_markdown)]
61// Because the current MSRV is 1.65
62#![allow(clippy::non_std_lazy_statics)]
63
64/// Expands import statements for `fakesimd` or `std::simd`.
65macro_rules! import_simd {
66    (as $modalias:ident) => {
67        #[cfg(feature = "simd-nightly")]
68        use std::simd as $modalias;
69        #[cfg(not(feature = "simd-nightly"))]
70        use $crate::fakesimd as $modalias;
71
72        #[allow(unused_imports)]
73        use simd::prelude::*;
74
75        #[allow(unused_imports)]
76        use simd::StdFloat;
77    };
78}
79
80/// Sets up the thread-local re-usable storage for avoiding reallocation.
81///
82/// This provides a short-cut for the common pattern using [`thread_local!`]
83/// and [`RefCell`].  Currently, this is just for removing small repetition in
84/// code.
85///
86/// NOTE: This is tentatively brought to the global space for avoiding
87/// unintended exposure in the document. Probably, this should be moved to an
88/// appropriate sub-modules after we learn how to control visibility of macros
89/// defined in a sub-module.
90///
91/// [`RefCell`]: std::cell::RefCell
92macro_rules! reusable {
93    ($key:ident: $t:ty) => {
94        thread_local! {
95            static $key: std::cell::RefCell<$t> = std::cell::RefCell::new(Default::default());
96        }
97    };
98    ($key:ident: $t:ty = $init:expr) => {
99        thread_local! {
100            static $key: std::cell::RefCell<$t> = std::cell::RefCell::new($init);
101        }
102    };
103}
104
105/// Macro used when using a storage declared using [`reusable!`].
106///
107/// NOTE: This is tentatively brought to the global space for avoiding
108/// unintended exposure in the document. Probably, this should be moved to an
109/// appropriate sub-modules after we learn how to control visibility of macros
110/// defined in a sub-module.
111macro_rules! reuse {
112    ($key:ident, $fn:expr) => {{
113        #[allow(clippy::redundant_closure_call)]
114        $key.with(|cell| $fn(&mut cell.borrow_mut()))
115    }};
116}
117
118// internal log macros
119#[allow(unused)]
120macro_rules! info { (target: $t:literal, $($x:tt)*) => (
121    #[cfg(feature = "log")] {
122        log::info!(target: $t, $($x)*)
123    }
124) }
125
126pub(crate) mod arrayutils;
127pub mod bitsink;
128pub(crate) mod coding;
129pub mod component;
130pub mod config;
131pub mod constant;
132pub mod error;
133#[cfg(not(feature = "simd-nightly"))]
134#[doc(hidden)]
135pub(crate) mod fakesimd;
136pub(crate) mod lpc;
137#[cfg(feature = "par")]
138pub(crate) mod par;
139pub(crate) mod repeat;
140pub(crate) mod rice;
141#[cfg(any(test, feature = "__export_sigen"))]
142#[doc(hidden)]
143pub mod sigen;
144pub mod source;
145
146#[cfg(test)]
147pub mod test_helper;
148
149// this is for including "doctest_helper.rs" in lintting and auto-formating.
150#[cfg(clippy)]
151mod doctest_helper;
152
153#[cfg(feature = "mimalloc")]
154use mimalloc::MiMalloc;
155#[cfg(feature = "mimalloc")]
156#[global_allocator]
157static GLOBAL: MiMalloc = MiMalloc;
158
159// import global entry points
160pub use coding::encode_fixed_size_frame;
161
162pub use coding::encode_with_fixed_block_size;
163
164#[cfg(test)]
165mod tests {
166    // end-to-end, but transparent test.
167    #[cfg(feature = "serde")]
168    use super::error::Verify;
169    #[cfg(feature = "serde")]
170    use super::*;
171    #[cfg(feature = "serde")]
172    use crate::sigen::Signal;
173    #[cfg(feature = "serde")]
174    use rstest::rstest;
175
176    #[allow(dead_code)] // only used when `cfg(feature = "serde")`.
177    const FIXED_BLOCK_CONFIGS: [&str; 5] = [
178        "",
179        r"
180block_sizes = [512]
181        ",
182        r"
183block_sizes = [123]
184        ",
185        r"
186block_sizes = [1024]
187[subframe_coding.qlpc]
188use_direct_mse = true
189mae_optimization_steps = 2
190        ",
191        r"
192multithread = false
193        ",
194    ];
195
196    /// E2E testing involving integrity check with `claxon` decoder.
197    ///
198    /// The test signals used in this test are just sinusoids mixed with small white noise. All
199    /// possible combinations of the selected numbers of channels, sampling rates, and config
200    /// strings are evaluated.
201    #[cfg(feature = "serde")]
202    #[rstest]
203    fn e2e_with_generated_sinusoids(
204        #[values(1, 2, 3, 5, 8)] channels: usize,
205        #[values(16000, 16001, 95800)] sample_rate: usize,
206        #[values(FIXED_BLOCK_CONFIGS[0],
207                 FIXED_BLOCK_CONFIGS[1],
208                 FIXED_BLOCK_CONFIGS[2],
209                 FIXED_BLOCK_CONFIGS[3],
210                 FIXED_BLOCK_CONFIGS[4])]
211        config: &str,
212    ) {
213        let signal_len = 16123;
214        let bits_per_sample = 16;
215
216        let mut channel_signals = vec![];
217        for _ch in 0..channels {
218            channel_signals.push(
219                sigen::Sine::new(36, 0.4)
220                    .noise(0.04)
221                    .to_vec_quantized(bits_per_sample, signal_len),
222            );
223        }
224
225        let mut signal = vec![];
226        for t in 0..signal_len {
227            for s in &channel_signals {
228                signal.push(s[t]);
229            }
230        }
231        let mut config: config::Encoder = toml::from_str(config).expect("config parsing error");
232
233        if !cfg!(feature = "experimental") {
234            // disable experimental features
235            config.subframe_coding.qlpc.use_direct_mse = false;
236            config.subframe_coding.qlpc.mae_optimization_steps = 0;
237        }
238
239        let config = config.into_verified().expect("config value error");
240
241        let source =
242            source::MemSource::from_samples(&signal, channels, bits_per_sample, sample_rate);
243
244        test_helper::integrity_test(
245            |s| {
246                coding::encode_with_fixed_block_size(&config, s, config.block_size)
247                    .expect("source error")
248            },
249            &source,
250        );
251    }
252
253    reusable!(REUSABLE_BUF: Vec<i32>);
254
255    #[test]
256    fn call_twice() {
257        fn fn1() {
258            reuse!(REUSABLE_BUF, |buf: &mut Vec<i32>| {
259                assert_eq!(buf.len(), 0);
260                buf.resize(5, 0i32);
261            });
262        }
263
264        fn fn2() {
265            reuse!(REUSABLE_BUF, |buf: &mut Vec<i32>| {
266                assert_eq!(buf.len(), 5);
267            });
268        }
269
270        fn1();
271        fn2();
272    }
273}