├── .gitignore ├── tests ├── c │ ├── version.txt │ ├── calc_gating_block.c │ ├── true_peak.c │ ├── interp.c │ ├── filter.c │ ├── history.c │ └── queue.h └── reference_tests.rs ├── capi-test ├── Makefile └── capi-test.c ├── LICENSE ├── examples ├── normalize.rs ├── generate_histogram_bins.rs └── replaygain.rs ├── README.md ├── Cargo.toml ├── benches ├── calc_gating_block.rs ├── interp.rs ├── history.rs ├── true_peak.rs ├── filter.rs └── ebur128.rs ├── src ├── lib.rs ├── interp.rs ├── capi.rs ├── true_peak.rs └── utils.rs ├── .github └── workflows │ └── ebur128.yml ├── CHANGELOG.md └── assets └── ebur128.h /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | tests/reference_files 5 | -------------------------------------------------------------------------------- /tests/c/version.txt: -------------------------------------------------------------------------------- 1 | Based on 68abf1bd0a904b4bae613f9aeed85e215af1c4e3 from https://github.com/jiixyj/libebur128 2 | 3 | Relevant C functions extracted for comparison tests with the Rust version. 4 | -------------------------------------------------------------------------------- /capi-test/Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | CFLAGS ?= -O2 -g -Wall 3 | 4 | all: capi-test 5 | 6 | capi-test: capi-test.c 7 | $(CC) -o capi-test $(CFLAGS) capi-test.c $(shell pkg-config --libs --cflags libebur128) -lm 8 | 9 | check: capi-test 10 | LD_LIBRARY_PATH=$(shell pkg-config --variable=libdir libebur128) ./capi-test 11 | 12 | check-valgrind: capi-test 13 | LD_LIBRARY_PATH=$(shell pkg-config --variable=libdir libebur128) valgrind --error-exitcode=-1 --track-origins=yes --leak-check=full ./capi-test 14 | 15 | clean: 16 | rm -f capi-test 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Jan Kokemüller 2 | Copyright (c) 2020 Sebastian Dröge 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /tests/c/calc_gating_block.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define _USE_MATH_DEFINES 4 | #include 5 | #include 6 | #include 7 | 8 | double calc_gating_block_c(size_t frames_per_block, const double *audio_data, size_t audio_data_frames, size_t audio_data_index, unsigned int *channel_map, size_t channels) { 9 | size_t i, c; 10 | double sum = 0.0; 11 | double channel_sum; 12 | for (c = 0; c < channels; ++c) { 13 | if (channel_map[c] == 0 /* EBUR128_UNUSED */) { 14 | continue; 15 | } 16 | channel_sum = 0.0; 17 | if (audio_data_index < frames_per_block * channels) { 18 | for (i = 0; i < audio_data_index / channels; ++i) { 19 | channel_sum += audio_data[i * channels + c] * 20 | audio_data[i * channels + c]; 21 | } 22 | for (i = audio_data_frames - 23 | (frames_per_block - audio_data_index / channels); 24 | i < audio_data_frames; ++i) { 25 | channel_sum += audio_data[i * channels + c] * 26 | audio_data[i * channels + c]; 27 | } 28 | } else { 29 | for (i = audio_data_index / channels - frames_per_block; 30 | i < audio_data_index / channels; ++i) { 31 | channel_sum += audio_data[i * channels + c] * 32 | audio_data[i * channels + c]; 33 | } 34 | } 35 | if (channel_map[c] == 4 /* EBUR128_Mp110 */ || 36 | channel_map[c] == 5 /* EBUR128_Mm110 */ || 37 | channel_map[c] == 9 /* EBUR128_Mp060 */ || 38 | channel_map[c] == 10 /* EBUR128_Mm060 */ || 39 | channel_map[c] == 11 /* EBUR128_Mp090 */ || 40 | channel_map[c] == 12 /* EBUR128_Mm090 */) { 41 | channel_sum *= 1.41; 42 | } else if (channel_map[c] == 6 /* EBUR128_DUAL_MONO */) { 43 | channel_sum *= 2.0; 44 | } 45 | sum += channel_sum; 46 | } 47 | 48 | sum /= (double) frames_per_block; 49 | 50 | return sum; 51 | } 52 | -------------------------------------------------------------------------------- /examples/normalize.rs: -------------------------------------------------------------------------------- 1 | /* 2 | wget https://github.com/thewh1teagle/vibe/raw/main/samples/multi.wav 3 | cargo run --example normalize multi.wav normalized.wav 4 | */ 5 | use ebur128::{EbuR128, Mode}; 6 | use hound::{WavReader, WavSpec, WavWriter}; 7 | 8 | fn main() { 9 | let input_path = std::env::args() 10 | .nth(1) 11 | .expect("Please specify input wav path"); 12 | let output_path = std::env::args() 13 | .nth(2) 14 | .expect("Please specify output wav path"); 15 | let target_loudness = -23.0; // EBU R128 standard target loudness 16 | 17 | let mut reader = WavReader::open(&input_path).expect("Failed to open WAV file"); 18 | 19 | let spec = reader.spec(); 20 | let channels = spec.channels as usize; 21 | let rate = spec.sample_rate; 22 | let mut ebur128 = 23 | EbuR128::new(channels as u32, rate, Mode::all()).expect("Failed to create ebur128"); 24 | 25 | let samples: Vec = reader 26 | .samples::() 27 | .map(|s| s.unwrap() as f32 / i16::MAX as f32) 28 | .collect(); 29 | let chunk_size = rate; // 1s 30 | 31 | // Compute loudness 32 | for chunk in samples.chunks(chunk_size as usize * channels) { 33 | ebur128.add_frames_f32(chunk).expect("Failed to add frames"); 34 | ebur128 35 | .loudness_global() 36 | .expect("Failed to get global loudness"); 37 | } 38 | 39 | let global_loudness = ebur128 40 | .loudness_global() 41 | .expect("Failed to get global loudness"); 42 | 43 | // Convert dB difference to linear gain 44 | let gain = 10f32.powf(((target_loudness - global_loudness) / 20.0) as f32); 45 | 46 | let mut writer = WavWriter::create( 47 | output_path, 48 | WavSpec { 49 | channels: spec.channels, 50 | sample_rate: spec.sample_rate, 51 | bits_per_sample: spec.bits_per_sample, 52 | sample_format: spec.sample_format, 53 | }, 54 | ) 55 | .expect("Failed to create WAV writer"); 56 | 57 | for sample in samples { 58 | let normalized_sample = (sample * gain).clamp(-1.0, 1.0); 59 | writer 60 | .write_sample((normalized_sample * i16::MAX as f32) as i16) 61 | .expect("Failed to write sample"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ebur128 [![crates.io](https://img.shields.io/crates/v/ebur128.svg)](https://crates.io/crates/ebur128) [![Actions Status](https://github.com/sdroege/ebur128/workflows/ebur128/badge.svg)](https://github.com/sdroege/ebur128/actions) [![docs.rs](https://docs.rs/ebur128/badge.svg)](https://docs.rs/ebur128) 2 | 3 | Implementation of the [EBU R128 loudness standard](https://tech.ebu.ch/docs/r/r128.pdf). 4 | 5 | The European Broadcasting Union Loudness Recommendation (EBU R128) informs broadcasters how 6 | they can analyze and normalize audio so that each piece of audio sounds roughly the same 7 | volume to the human ear. 8 | 9 | This crate provides an API which analyzes audio and outputs perceived loudness. The results 10 | can then be used to normalize volume during playback. 11 | 12 | Features: 13 | * Implements M, S and I modes ([EBU - TECH 3341](https://tech.ebu.ch/docs/tech/tech3341.pdf)) 14 | * Implements loudness range measurement ([EBU - TECH 3342](https://tech.ebu.ch/docs/tech/tech3342.pdf)) 15 | * True peak scanning 16 | * Supports all samplerates by recalculation of the filter coefficients 17 | 18 | This crate is a Rust port of the [libebur128](https://github.com/jiixyj/libebur128) C library, produces the 19 | same results as the C library and has comparable performance. 20 | 21 | ## EBU TECH 3341/3342 Compliance 22 | 23 | Currently, the implementation passes all tests defined in [EBU - TECH 3341](https://tech.ebu.ch/docs/tech/tech3341.pdf) 24 | and [EBU - TECH 3342](https://tech.ebu.ch/docs/tech/tech3342.pdf). 25 | 26 | ## C API 27 | 28 | ebur128 optionally provides a C API that is API/ABI-compatible with 29 | libebur128. It can be built and installed via [`cargo-c`](https://crates.io/crates/cargo-c): 30 | 31 | ```sh 32 | # If cargo-c was not installed yet 33 | $ cargo install cargo-c 34 | # Change the prefix to the place where it should be installed 35 | $ cargo cbuild --prefix /usr/local 36 | $ cargo cinstall --prefix /usr/local 37 | ``` 38 | 39 | This installs a shared library, static library, C header and [`pkg-config`](https://www.freedesktop.org/wiki/Software/pkg-config/) 40 | file that is compatible with libebur128. 41 | 42 | ## LICENSE 43 | 44 | ebur128 is licensed under the MIT license ([LICENSE](LICENSE) or 45 | http://opensource.org/licenses/MIT). 46 | 47 | ## Contribution 48 | 49 | Any kinds of contributions are welcome as a pull request. 50 | 51 | Unless you explicitly state otherwise, any contribution intentionally 52 | submitted for inclusion in ebur128 by you shall be licensed under the MIT 53 | license as above, without any additional terms or conditions. 54 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ebur128" 3 | version = "0.1.10" 4 | authors = ["Sebastian Dröge "] 5 | edition = "2018" 6 | categories = ["multimedia"] 7 | keywords = ["multimedia", "audio", "dsp"] 8 | description = "Implementation of the EBU R128 loudness standard" 9 | repository = "https://github.com/sdroege/ebur128" 10 | homepage = "https://github.com/sdroege/ebur128" 11 | license = "MIT" 12 | readme = "README.md" 13 | rust-version = "1.60" 14 | 15 | [dependencies] 16 | bitflags = "1.0" 17 | smallvec = "1.0" 18 | dasp_sample = "0.11" 19 | dasp_frame = "0.11" 20 | 21 | [build-dependencies] 22 | cc = { version = "1.0", optional = true } 23 | 24 | [dev-dependencies] 25 | criterion = "0.4" 26 | float_eq = "1" 27 | ebur128-c = { package = "ebur128", version = "=0.1.1" } 28 | quickcheck = "0.9" 29 | quickcheck_macros = "0.9" 30 | rand = "0.7" 31 | hound = "3" 32 | 33 | [features] 34 | internal-tests = [] 35 | c-tests = ["cc", "internal-tests"] # and ebur128-c, quickcheck, quickcheck_macros, rand but dev-dependencies can't be optional... 36 | reference-tests = [] 37 | capi = [] 38 | 39 | # Enabling this increases the precision of true-peak calculation slightly, but causes a significant 40 | # performance-hit in the default build-configuration. To avoid the performance-hit, also enable 41 | # `RUSTFLAGS=-C target-feature=+fma`, assuming your platform supports it 42 | precision-true-peak = [] 43 | 44 | 45 | [lib] 46 | name = "ebur128" 47 | 48 | [package.metadata.capi] 49 | min_version = "0.9.1" 50 | 51 | [package.metadata.capi.header] 52 | name = "ebur128.h" 53 | subdirectory = false 54 | generation = false 55 | 56 | [package.metadata.capi.pkg_config] 57 | name = "libebur128" 58 | filename = "libebur128" 59 | description = "EBU R 128 standard for loudness normalisation" 60 | version = "1.2.6" 61 | 62 | [package.metadata.capi.library] 63 | name = "ebur128" 64 | version = "1.2.6" 65 | 66 | [[bench]] 67 | name = "interp" 68 | harness = false 69 | required-features = ["internal-tests"] 70 | 71 | [[bench]] 72 | name = "true_peak" 73 | harness = false 74 | required-features = ["internal-tests"] 75 | 76 | [[bench]] 77 | name = "history" 78 | harness = false 79 | required-features = ["internal-tests"] 80 | 81 | [[bench]] 82 | name = "filter" 83 | harness = false 84 | required-features = ["internal-tests"] 85 | 86 | [[bench]] 87 | name = "calc_gating_block" 88 | harness = false 89 | required-features = ["internal-tests"] 90 | 91 | [[bench]] 92 | name = "ebur128" 93 | harness = false 94 | 95 | [[test]] 96 | name = "reference_tests" 97 | required-features = ["reference-tests"] 98 | 99 | [profile.dev] 100 | opt-level = 1 101 | -------------------------------------------------------------------------------- /benches/calc_gating_block.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | #[cfg(feature = "c-tests")] 4 | fn calc_gating_block_c( 5 | frames_per_block: usize, 6 | audio_data: &[f64], 7 | audio_data_index: usize, 8 | channel_map: &[u32], 9 | ) -> f64 { 10 | unsafe { 11 | ebur128::filter::calc_gating_block_c( 12 | frames_per_block, 13 | audio_data.as_ptr(), 14 | audio_data.len() / channel_map.len(), 15 | audio_data_index, 16 | channel_map.as_ptr(), 17 | channel_map.len(), 18 | ) 19 | } 20 | } 21 | 22 | #[cfg(feature = "internal-tests")] 23 | fn calc_gating_block( 24 | frames_per_block: usize, 25 | audio_data: &[f64], 26 | audio_data_index: usize, 27 | channel_map: &[ebur128::Channel], 28 | ) -> f64 { 29 | ebur128::filter::Filter::calc_gating_block( 30 | frames_per_block, 31 | audio_data, 32 | audio_data_index, 33 | channel_map, 34 | ) 35 | } 36 | 37 | pub fn criterion_benchmark(c: &mut Criterion) { 38 | #[cfg(feature = "internal-tests")] 39 | { 40 | let mut data = vec![0f64; 48_000 * 3 * 2]; 41 | let mut accumulator = 0.0; 42 | let step = 2.0 * std::f64::consts::PI * 440.0 / 48_000.0; 43 | for out in data.chunks_exact_mut(2) { 44 | let val = f64::sin(accumulator); 45 | out[0] = val; 46 | out[1] = val; 47 | accumulator += step; 48 | } 49 | 50 | let channel_map = [ebur128::Channel::Left; 2]; 51 | 52 | let frames_per_block = 144_000; 53 | 54 | let mut group = c.benchmark_group("calc gating block: 48kHz 2ch"); 55 | 56 | #[cfg(feature = "c-tests")] 57 | { 58 | let channel_map_c = [1; 2]; 59 | 60 | group.bench_function("C", |b| { 61 | b.iter(|| { 62 | calc_gating_block_c( 63 | black_box(frames_per_block), 64 | black_box(&data), 65 | black_box(0), 66 | black_box(&channel_map_c), 67 | ) 68 | }) 69 | }); 70 | } 71 | 72 | group.bench_function("Rust", |b| { 73 | b.iter(|| { 74 | calc_gating_block( 75 | black_box(frames_per_block), 76 | black_box(&data), 77 | black_box(0), 78 | black_box(&channel_map), 79 | ) 80 | }) 81 | }); 82 | 83 | group.finish(); 84 | } 85 | } 86 | 87 | criterion_group!(benches, criterion_benchmark); 88 | criterion_main!(benches); 89 | -------------------------------------------------------------------------------- /capi-test/capi-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #define _USE_MATH_DEFINES 5 | #include 6 | 7 | #include 8 | 9 | #define assert_int_eq(a, b) do { \ 10 | if ((a) != (b)) { \ 11 | fprintf(stderr, "%s:%d: assertion failed: \"%s\" (%d) != \"%s\" (%d)\n", __FILE__, __LINE__, #a, (a), #b, (b)); \ 12 | exit(-1); \ 13 | } \ 14 | } while(0); 15 | 16 | #define assert_double_eq(a, b) do { \ 17 | if (fabs((a) - (b)) >= 0.000001) { \ 18 | fprintf(stderr, "%s:%d: assertion failed: \"%s\" (%lf) != \"%s\" (%lf)\n", __FILE__, __LINE__, #a, (a), #b, (b)); \ 19 | exit(-1); \ 20 | } \ 21 | } while(0); 22 | 23 | int main(int argc, char **argv) { 24 | ebur128_state *s; 25 | float *data; 26 | size_t i; 27 | float acc, step; 28 | double val; 29 | const unsigned long sample_rate = 48000; 30 | const int channels = 2; 31 | const size_t num_frames = sample_rate * 5; 32 | 33 | s = ebur128_init(channels, sample_rate, EBUR128_MODE_M | EBUR128_MODE_S | EBUR128_MODE_I | EBUR128_MODE_LRA | EBUR128_MODE_SAMPLE_PEAK | EBUR128_MODE_TRUE_PEAK); 34 | 35 | data = malloc(num_frames * channels * sizeof (*data)); 36 | acc = 0.0; 37 | step = 2.0 * M_PI * 440.0 / sample_rate; 38 | for (i = 0; i < num_frames; i++) { 39 | size_t j; 40 | 41 | val = sinf(acc); 42 | 43 | for (j = 0; j < channels; j++) 44 | data[i*channels + j] = val; 45 | 46 | acc += step; 47 | } 48 | 49 | assert_int_eq(ebur128_add_frames_float(s, data, num_frames), EBUR128_SUCCESS); 50 | 51 | assert_int_eq(ebur128_loudness_global(s, &val), EBUR128_SUCCESS); 52 | assert_double_eq(val, -0.6826039914165554); 53 | 54 | assert_int_eq(ebur128_loudness_momentary(s, &val), EBUR128_SUCCESS); 55 | assert_double_eq(val, -0.6813325598268921); 56 | 57 | assert_int_eq(ebur128_loudness_shortterm(s, &val), EBUR128_SUCCESS); 58 | assert_double_eq(val, -0.6827591715100236); 59 | 60 | assert_int_eq(ebur128_loudness_window(s, 1, &val), EBUR128_SUCCESS); 61 | assert_double_eq(val, -0.8742956620008693); 62 | 63 | assert_int_eq(ebur128_loudness_range(s, &val), EBUR128_SUCCESS); 64 | assert_double_eq(val, 0.00006921150169403312); 65 | 66 | assert_int_eq(ebur128_relative_threshold(s, &val), EBUR128_SUCCESS); 67 | assert_double_eq(val, -10.682603991416554); 68 | 69 | for (i = 0; i < channels; i++) { 70 | assert_int_eq(ebur128_sample_peak(s, i, &val), EBUR128_SUCCESS); 71 | assert_double_eq(val, 1.0); 72 | assert_int_eq(ebur128_prev_sample_peak(s, i, &val), EBUR128_SUCCESS); 73 | assert_double_eq(val, 1.0); 74 | 75 | assert_int_eq(ebur128_true_peak(s, i, &val), EBUR128_SUCCESS); 76 | assert_double_eq(val, 1.0008491277694702); 77 | assert_int_eq(ebur128_prev_true_peak(s, i, &val), EBUR128_SUCCESS); 78 | assert_double_eq(val, 1.0008491277694702); 79 | } 80 | 81 | free(data); 82 | ebur128_destroy(&s); 83 | 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Jan Kokemüller 2 | // Copyright (c) 2020 Sebastian Dröge 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | //! Implementation of the [EBU R128 loudness standard](https://tech.ebu.ch/docs/r/r128.pdf). 23 | //! 24 | //! The European Broadcasting Union Loudness Recommendation (EBU R128) informs broadcasters how 25 | //! they can analyze and normalize audio so that each piece of audio sounds roughly the same 26 | //! volume to the human ear. 27 | //! 28 | //! This crate provides an API which analyzes audio and outputs perceived loudness. The results 29 | //! can then be used to normalize volume during playback. 30 | //! 31 | //! Features: 32 | //! * Implements M, S and I modes ([EBU - TECH 3341](https://tech.ebu.ch/docs/tech/tech3341.pdf)) 33 | //! * Implements loudness range measurement ([EBU - TECH 3342](https://tech.ebu.ch/docs/tech/tech3342.pdf)) 34 | //! * True peak scanning 35 | //! * Supports all samplerates by recalculation of the filter coefficients 36 | 37 | mod ebur128; 38 | pub use self::ebur128::*; 39 | pub use self::utils::energy_to_loudness; 40 | 41 | #[cfg(feature = "internal-tests")] 42 | pub mod interp; 43 | #[cfg(not(feature = "internal-tests"))] 44 | pub(crate) mod interp; 45 | 46 | #[cfg(feature = "internal-tests")] 47 | pub mod true_peak; 48 | #[cfg(not(feature = "internal-tests"))] 49 | pub(crate) mod true_peak; 50 | 51 | #[cfg(feature = "internal-tests")] 52 | pub mod history; 53 | #[cfg(not(feature = "internal-tests"))] 54 | pub(crate) mod history; 55 | 56 | #[allow(clippy::excessive_precision)] 57 | mod histogram_bins; 58 | 59 | #[cfg(feature = "internal-tests")] 60 | pub mod filter; 61 | #[cfg(not(feature = "internal-tests"))] 62 | pub(crate) mod filter; 63 | 64 | #[cfg(feature = "internal-tests")] 65 | pub mod utils; 66 | #[cfg(not(feature = "internal-tests"))] 67 | pub(crate) mod utils; 68 | 69 | #[cfg(feature = "internal-tests")] 70 | pub use utils::{Interleaved, Planar, Samples}; 71 | #[cfg(not(feature = "internal-tests"))] 72 | pub(crate) use utils::{Interleaved, Planar, Samples}; 73 | 74 | #[cfg(test)] 75 | pub mod tests { 76 | pub use super::utils::tests::Signal; 77 | } 78 | 79 | #[cfg(feature = "capi")] 80 | #[allow(clippy::missing_safety_doc)] 81 | pub mod capi; 82 | -------------------------------------------------------------------------------- /.github/workflows/ebur128.yml: -------------------------------------------------------------------------------- 1 | name: ebur128 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | rustfmt-clippy: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Install stable 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | override: true 25 | components: clippy, rustfmt 26 | 27 | - name: Run rustfmt 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: fmt 31 | args: -- --check 32 | 33 | - name: Run clippy 34 | uses: actions-rs/clippy-check@v1 35 | with: 36 | token: ${{ secrets.GITHUB_TOKEN }} 37 | args: --all-targets --all-features -- -D warnings 38 | 39 | cargo-c-tests: 40 | 41 | runs-on: ubuntu-latest 42 | 43 | strategy: 44 | matrix: 45 | toolchain: [stable, beta, nightly] 46 | 47 | steps: 48 | - uses: actions/checkout@v2 49 | 50 | - name: Install valgrind 51 | run: | 52 | sudo apt-get update 53 | sudo apt-get install valgrind 54 | 55 | - name: Install ${{ matrix.toolchain }} 56 | uses: actions-rs/toolchain@v1 57 | with: 58 | profile: minimal 59 | toolchain: ${{ matrix.toolchain }} 60 | override: true 61 | 62 | - name: Install cargo-c 63 | env: 64 | LINK: https://github.com/lu-zero/cargo-c/releases/download 65 | CARGO_C_VERSION: 0.9.13 66 | run: | 67 | curl -L "$LINK/v$CARGO_C_VERSION/cargo-c-linux.tar.gz" | 68 | tar xz -C $HOME/.cargo/bin 69 | 70 | - name: Run cargo-c 71 | run: | 72 | cargo cinstall --prefix=$HOME/install 73 | 74 | - name: Set pkg-config path 75 | run: | 76 | INSTALL_PATH=$HOME/install/lib/pkgconfig 77 | echo "PKG_CONFIG_PATH=$INSTALL_PATH" >> $GITHUB_ENV 78 | 79 | - name: Check capi 80 | run: | 81 | make -C capi-test check 82 | 83 | - name: Check capi valgrind 84 | run: | 85 | make -C capi-test check-valgrind 86 | 87 | ubuntu-tests: 88 | 89 | runs-on: ubuntu-latest 90 | 91 | strategy: 92 | matrix: 93 | toolchain: [stable, beta, nightly] 94 | 95 | steps: 96 | - uses: actions/checkout@v2 97 | 98 | - name: Download samples 99 | env: 100 | LINK: https://tech.ebu.ch/files/live/sites/tech/files/shared 101 | run: | 102 | wget $LINK/testmaterial/ebu-loudness-test-setv05.zip 103 | unzip -u ebu-loudness-test-setv05.zip -d tests/reference_files 104 | 105 | - name: Install ${{ matrix.toolchain }} 106 | uses: actions-rs/toolchain@v1 107 | with: 108 | profile: minimal 109 | toolchain: ${{ matrix.toolchain }} 110 | override: true 111 | 112 | - name: Run tests 113 | env: 114 | QUICKCHECK_TESTS: 2 115 | run: | 116 | cargo test --features c-tests,internal-tests,reference-tests 117 | 118 | msrv-ubuntu-tests: 119 | 120 | runs-on: ubuntu-latest 121 | 122 | strategy: 123 | matrix: 124 | toolchain: ['1.60'] 125 | 126 | steps: 127 | - uses: actions/checkout@v2 128 | 129 | - name: Install ${{ matrix.toolchain }} 130 | uses: actions-rs/toolchain@v1 131 | with: 132 | profile: minimal 133 | toolchain: ${{ matrix.toolchain }} 134 | override: true 135 | 136 | - name: Use MSRV Cargo.lock 137 | run: cp Cargo.lock.msrv Cargo.lock 138 | 139 | - name: Run cargo check 140 | run: | 141 | cargo check 142 | -------------------------------------------------------------------------------- /examples/generate_histogram_bins.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Jan Kokemüller 2 | // Copyright (c) 2020 Sebastian Dröge 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | use std::fs; 23 | use std::io::prelude::*; 24 | use std::path::PathBuf; 25 | 26 | fn main() { 27 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 28 | path.push("src"); 29 | path.push("histogram_bins.rs"); 30 | 31 | let mut contents = Vec::new(); 32 | 33 | let mut energies = [0.0f64; 1000]; 34 | for (i, o) in energies.iter_mut().enumerate() { 35 | *o = f64::powf(10.0, (i as f64 / 10.0 - 69.95 + 0.691) / 10.0); 36 | } 37 | 38 | let mut boundaries = [0.0f64; 1001]; 39 | for (i, o) in boundaries.iter_mut().enumerate() { 40 | *o = f64::powf(10.0, (i as f64 / 10.0 - 70.0 + 0.691) / 10.0); 41 | } 42 | 43 | write!( 44 | &mut contents, 45 | "\ 46 | // Copyright (c) 2011 Jan Kokemüller 47 | // Copyright (c) 2020 Sebastian Dröge 48 | // 49 | // Permission is hereby granted, free of charge, to any person obtaining a copy 50 | // of this software and associated documentation files (the \"Software\"), to deal 51 | // in the Software without restriction, including without limitation the rights 52 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 53 | // copies of the Software, and to permit persons to whom the Software is 54 | // furnished to do so, subject to the following conditions: 55 | // 56 | // The above copyright notice and this permission notice shall be included in 57 | // all copies or substantial portions of the Software. 58 | // 59 | // THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 60 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 61 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 62 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 63 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 64 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 65 | // THE SOFTWARE. 66 | 67 | // DO NOT EDIT: This file is autogenerated by `examples/generate_histogram_bins.rs` 68 | 69 | /// Histogram energies, i.e. energy at the center of each histogram bin. 70 | pub static ENERGIES: [f64; 1000] = {energies:#.prec$?}; 71 | 72 | /// Histogram boundaries, i.e. the energies between each histogram bin. 73 | pub static BOUNDARIES: [f64; 1001] = {boundaries:#.prec$?}; 74 | ", 75 | energies = &energies[..], 76 | boundaries = &boundaries[..], 77 | prec = 36, 78 | ) 79 | .expect("Failed to format file contents"); 80 | 81 | fs::write(path, contents).expect("Failed to write file"); 82 | } 83 | -------------------------------------------------------------------------------- /benches/interp.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use ebur128::interp; 4 | 5 | pub fn criterion_benchmark(c: &mut Criterion) { 6 | let mut data = vec![0f32; 48_000 * 5 * 2]; 7 | let mut accumulator = 0.0; 8 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 9 | for out in data.chunks_exact_mut(2) { 10 | let val = f32::sin(accumulator); 11 | out[0] = val; 12 | out[1] = val; 13 | accumulator += step; 14 | } 15 | let mut data_out = vec![0.0f32; 48_000 * 5 * 2 * 2]; 16 | 17 | let mut group = c.benchmark_group("interp create: 49 taps 2 factors 2ch"); 18 | 19 | #[cfg(feature = "c-tests")] 20 | unsafe { 21 | group.bench_function("C", |b| { 22 | b.iter(|| { 23 | let interp = interp::interp_create_c( 24 | black_box(49), 25 | black_box(data_out.len() / data.len()) as u32, 26 | black_box(2), 27 | ); 28 | interp::interp_destroy_c(interp); 29 | }) 30 | }); 31 | } 32 | group.bench_function("Rust", |b| { 33 | b.iter(|| { 34 | let _interp = interp::InterpF::<24, 2, [f32; 4]>::new(); 35 | }) 36 | }); 37 | 38 | group.finish(); 39 | 40 | let mut group = c.benchmark_group("interp process: 49 taps 2 factors 2ch"); 41 | 42 | #[cfg(feature = "c-tests")] 43 | unsafe { 44 | let interp = interp::interp_create_c( 45 | black_box(49), 46 | black_box(data_out.len() / data.len()) as u32, 47 | black_box(2), 48 | ); 49 | group.bench_function("C", |b| { 50 | b.iter(|| { 51 | interp::interp_process_c(interp, 48_000 * 5, data.as_ptr(), data_out.as_mut_ptr()); 52 | }) 53 | }); 54 | interp::interp_destroy_c(interp); 55 | } 56 | { 57 | let mut interp = interp::InterpF::<24, 2, _>::new(); 58 | let (_, data, _) = unsafe { data.align_to::<[f32; 2]>() }; 59 | let (_, data_out, _) = unsafe { data_out.align_to_mut::<[f32; 2]>() }; 60 | group.bench_function("Rust", |b| { 61 | b.iter(|| { 62 | for (input_frame, output_frames) in 63 | Iterator::zip(data.iter(), data_out.chunks_exact_mut(2)) 64 | { 65 | output_frames.copy_from_slice(&interp.interpolate(*input_frame)); 66 | } 67 | }) 68 | }); 69 | } 70 | 71 | group.finish(); 72 | 73 | let mut group = c.benchmark_group("interp process: 49 taps 4 factors 2ch"); 74 | let mut data_out = vec![0.0f32; 48_000 * 5 * 2 * 4]; 75 | 76 | #[cfg(feature = "c-tests")] 77 | unsafe { 78 | let interp = interp::interp_create_c( 79 | black_box(49), 80 | black_box(data_out.len() / data.len()) as u32, 81 | black_box(2), 82 | ); 83 | group.bench_function("C", |b| { 84 | b.iter(|| { 85 | interp::interp_process_c(interp, 48_000 * 5, data.as_ptr(), data_out.as_mut_ptr()); 86 | }) 87 | }); 88 | interp::interp_destroy_c(interp); 89 | } 90 | { 91 | let mut interp = interp::InterpF::<12, 4, _>::new(); 92 | let (_, data, _) = unsafe { data.align_to::<[f32; 2]>() }; 93 | let (_, data_out, _) = unsafe { data_out.align_to_mut::<[f32; 2]>() }; 94 | group.bench_function("Rust", |b| { 95 | b.iter(|| { 96 | for (input_frame, output_frames) in 97 | Iterator::zip(data.iter(), data_out.chunks_exact_mut(4)) 98 | { 99 | output_frames.copy_from_slice(&interp.interpolate(*input_frame)); 100 | } 101 | }) 102 | }); 103 | } 104 | 105 | group.finish(); 106 | } 107 | 108 | criterion_group!(benches, criterion_benchmark); 109 | criterion_main!(benches); 110 | -------------------------------------------------------------------------------- /examples/replaygain.rs: -------------------------------------------------------------------------------- 1 | use ebur128::{energy_to_loudness, EbuR128, Mode}; 2 | use hound::WavReader; 3 | use std::path::Path; 4 | 5 | /// ReplayGain 2.0 Reference Gain 6 | /// 7 | /// See the [ReplayGain 2.0 specification][rg2spec] for details. 8 | /// 9 | /// [rg2spec]: https://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification#Reference_level 10 | const REPLAYGAIN2_REFERENCE_LUFS: f64 = -18.0; 11 | 12 | struct TrackData { 13 | pub loudness: f64, 14 | pub peak: f64, 15 | pub gating_block_count: u64, 16 | pub energy: f64, 17 | } 18 | 19 | fn main() { 20 | let input_path = std::env::args() 21 | .nth(1) 22 | .expect("Please specify input wav directory path"); 23 | 24 | let mut album_peak: f64 = 0.0; 25 | let mut album_gating_block_count: u64 = 0; 26 | let mut album_energy: f64 = 0.0; 27 | 28 | for dir_entry in std::fs::read_dir(&input_path).expect("Failed to read directory path") { 29 | let dir_entry = dir_entry.expect("Failed to read dir entry"); 30 | let metadata = dir_entry.metadata().expect("Failed to read metadata"); 31 | if !metadata.is_file() { 32 | continue; 33 | } 34 | 35 | let track_data = analyze_file(&dir_entry.path()); 36 | let track_gain = REPLAYGAIN2_REFERENCE_LUFS - track_data.loudness; 37 | let track_peak = track_data.peak; 38 | 39 | println!("TRACK_PATH={}", dir_entry.path().display()); 40 | 41 | // ReplayGain 2.0 Track Gain, formatted according to "Table 3: Metadata keys and value 42 | // formatting" in the ["Metadata format" section in the ReplayGain 2.0 specification][rgmeta]. 43 | // 44 | // [rgmeta]: https://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification#Metadata_format 45 | println!("REPLAYGAIN_TRACK_GAIN={track_gain:.2} dB"); 46 | 47 | // ReplayGain 2.0 Track Peak, formatted according to "Table 3: Metadata keys and value 48 | // formatting" in the ["Metadata format" section in the ReplayGain 2.0 specification][rgmeta]. 49 | // 50 | // [rgmeta]: https://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification#Metadata_format 51 | println!("REPLAYGAIN_TRACK_PEAK={track_peak:.6}"); 52 | println!(); 53 | 54 | // Album peak is just the maximum peak on the album. 55 | album_peak = album_peak.max(track_peak); 56 | album_gating_block_count += track_data.gating_block_count; 57 | album_energy += track_data.energy; 58 | } 59 | 60 | let album_gain = REPLAYGAIN2_REFERENCE_LUFS 61 | - energy_to_loudness(album_energy / album_gating_block_count as f64); 62 | 63 | println!("REPLAYGAIN_ALBUM_GAIN={album_gain:.2} dB"); 64 | println!("REPLAYGAIN_ALBUM_PEAK={album_peak:.6}"); 65 | println!("REPLAYGAIN_REFERNCE_LOUDNESS={REPLAYGAIN2_REFERENCE_LUFS:.2} LUFS"); 66 | } 67 | 68 | fn analyze_file(input_path: &Path) -> TrackData { 69 | let mut reader = WavReader::open(input_path).expect("Failed to open WAV file"); 70 | 71 | let spec = reader.spec(); 72 | let channels = spec.channels as usize; 73 | let rate = spec.sample_rate; 74 | let mut ebur128 = 75 | EbuR128::new(channels as u32, rate, Mode::all()).expect("Failed to create ebur128"); 76 | 77 | let samples: Vec = reader 78 | .samples::() 79 | .map(|s| s.unwrap() as f32 / i16::MAX as f32) 80 | .collect(); 81 | let chunk_size = rate; // 1s 82 | 83 | // Compute loudness 84 | for chunk in samples.chunks(chunk_size as usize * channels) { 85 | ebur128.add_frames_f32(chunk).expect("Failed to add frames"); 86 | ebur128 87 | .loudness_global() 88 | .expect("Failed to get global loudness"); 89 | } 90 | 91 | let loudness = ebur128 92 | .loudness_global() 93 | .expect("Failed to get global loudness"); 94 | let peak = (0..channels) 95 | .map(|channel_index| ebur128.sample_peak(channel_index as u32)) 96 | .try_fold(0.0f64, |a, b| b.map(|b| a.max(b))) 97 | .expect("Failed to determine peak"); 98 | let (gating_block_count, energy) = ebur128 99 | .gating_block_count_and_energy() 100 | .expect("failed to get gating block count and loudness"); 101 | 102 | TrackData { 103 | loudness, 104 | peak, 105 | gating_block_count, 106 | energy, 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/c/true_peak.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define _USE_MATH_DEFINES 4 | #include 5 | #include 6 | #include 7 | 8 | typedef void * interpolator; 9 | extern interpolator* interp_create_c(unsigned int taps, unsigned int factor, unsigned int channels); 10 | extern void interp_destroy_c(interpolator* interp); 11 | extern size_t interp_process_c(interpolator* interp, size_t frames, float* in, float* out); 12 | 13 | #define EBUR128_MAX(a, b) (((a) > (b)) ? (a) : (b)) 14 | 15 | typedef struct { 16 | interpolator *interp; 17 | unsigned int interp_factor; 18 | unsigned int channels; 19 | float* resampler_buffer_input; 20 | size_t resampler_buffer_input_frames; 21 | float* resampler_buffer_output; 22 | size_t resampler_buffer_output_frames; 23 | } true_peak; 24 | 25 | true_peak* true_peak_create_c(unsigned int rate, unsigned int channels) { 26 | unsigned int samples_in_100ms = ((rate + 5) / 10); 27 | true_peak* tp = (true_peak*) calloc(1, sizeof(true_peak)); 28 | tp->channels = channels; 29 | 30 | if (rate < 96000) { 31 | tp->interp = interp_create_c(49, 4, channels); 32 | tp->interp_factor = 4; 33 | } else if (rate < 192000) { 34 | tp->interp = interp_create_c(49, 2, channels); 35 | tp->interp_factor = 2; 36 | } else { 37 | free(tp); 38 | return NULL; 39 | } 40 | 41 | tp->resampler_buffer_input_frames = samples_in_100ms * 4; 42 | tp->resampler_buffer_input = (float*) malloc( 43 | tp->resampler_buffer_input_frames * channels * sizeof(float)); 44 | 45 | tp->resampler_buffer_output_frames = 46 | tp->resampler_buffer_input_frames * tp->interp_factor; 47 | tp->resampler_buffer_output = (float*) malloc( 48 | tp->resampler_buffer_output_frames * channels * sizeof(float)); 49 | 50 | return tp; 51 | } 52 | 53 | void true_peak_destroy_c(true_peak* tp) { 54 | if (!tp) 55 | return; 56 | 57 | free(tp->resampler_buffer_input); 58 | free(tp->resampler_buffer_output); 59 | free(tp); 60 | } 61 | 62 | #define EBUR128_CHECK_TRUE_PEAK(type, min_scale, max_scale) \ 63 | void true_peak_check_##type##_c(true_peak* tp, size_t frames, const type* src, double* peaks) { \ 64 | static double scaling_factor = \ 65 | EBUR128_MAX(-((double) (min_scale)), (double) (max_scale)); \ 66 | size_t c, i, frames_out; \ 67 | \ 68 | for (i = 0; i < frames; ++i) { \ 69 | for (c = 0; c < tp->channels; ++c) { \ 70 | tp->resampler_buffer_input[i * tp->channels + c] = \ 71 | (float) ((double) src[i * tp->channels + c] / scaling_factor); \ 72 | } \ 73 | } \ 74 | \ 75 | frames_out = \ 76 | interp_process_c(tp->interp, frames, tp->resampler_buffer_input, \ 77 | tp->resampler_buffer_output); \ 78 | \ 79 | for (i = 0; i < frames_out; ++i) { \ 80 | for (c = 0; c < tp->channels; ++c) { \ 81 | double val = \ 82 | (double) tp->resampler_buffer_output[i * tp->channels + c]; \ 83 | \ 84 | if (EBUR128_MAX(val, -val) > peaks[c]) { \ 85 | peaks[c] = EBUR128_MAX(val, -val); \ 86 | } \ 87 | } \ 88 | } \ 89 | } 90 | 91 | EBUR128_CHECK_TRUE_PEAK(short, SHRT_MIN, SHRT_MAX); 92 | EBUR128_CHECK_TRUE_PEAK(int, INT_MIN, INT_MAX); 93 | EBUR128_CHECK_TRUE_PEAK(float, -1.0, 1.0); 94 | EBUR128_CHECK_TRUE_PEAK(double, -1.0, 1.0); 95 | -------------------------------------------------------------------------------- /benches/history.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use ebur128::history; 4 | 5 | pub fn criterion_benchmark(c: &mut Criterion) { 6 | let mut energies = vec![0.0; 1_000_000]; 7 | for (i, e) in energies.iter_mut().enumerate() { 8 | *e = f64::powf(10.0, ((i % 1000) as f64 / 10.0 - 69.95 + 0.691) / 10.0); 9 | } 10 | 11 | // Initialize histogram state in C and Rust code first 12 | #[cfg(feature = "c-tests")] 13 | unsafe { 14 | history::history_init_c(); 15 | } 16 | drop(black_box(history::History::new(true, 0))); 17 | 18 | for (histogram, name) in &[(true, "Histogram"), (false, "Queue")] { 19 | let mut group = c.benchmark_group(format!("history add: 1M {name}")); 20 | #[cfg(feature = "c-tests")] 21 | { 22 | group.bench_function("C", |b| { 23 | b.iter(|| unsafe { 24 | let hist = history::history_create_c(i32::from(*histogram), 100_000); 25 | 26 | for e in black_box(&energies) { 27 | history::history_add_c(hist, *e); 28 | } 29 | 30 | history::history_destroy_c(hist); 31 | }) 32 | }); 33 | } 34 | group.bench_function("Rust", |b| { 35 | b.iter(|| { 36 | let mut hist = history::History::new(*histogram, 100_000); 37 | for e in black_box(&energies) { 38 | hist.add(*e); 39 | } 40 | }) 41 | }); 42 | group.finish(); 43 | 44 | let mut group = c.benchmark_group(format!("history gated loudness: 1M {name}")); 45 | #[cfg(feature = "c-tests")] 46 | unsafe { 47 | let hist = history::history_create_c(i32::from(*histogram), 100_000); 48 | 49 | for e in black_box(&energies) { 50 | history::history_add_c(hist, *e); 51 | } 52 | 53 | group.bench_function("C", |b| { 54 | b.iter(|| { 55 | black_box(history::history_gated_loudness_c(hist)); 56 | }) 57 | }); 58 | 59 | history::history_destroy_c(hist); 60 | } 61 | { 62 | let mut hist = history::History::new(*histogram, 100_000); 63 | 64 | for e in black_box(&energies) { 65 | hist.add(*e); 66 | } 67 | 68 | group.bench_function("Rust", |b| { 69 | b.iter(|| { 70 | black_box(hist.gated_loudness()); 71 | }) 72 | }); 73 | } 74 | group.finish(); 75 | 76 | let mut group = c.benchmark_group(format!("history relative threshold: 1M {name}")); 77 | #[cfg(feature = "c-tests")] 78 | unsafe { 79 | let hist = history::history_create_c(i32::from(*histogram), 100_000); 80 | 81 | for e in black_box(&energies) { 82 | history::history_add_c(hist, *e); 83 | } 84 | 85 | group.bench_function("C", |b| { 86 | b.iter(|| { 87 | black_box(history::history_relative_threshold_c(hist)); 88 | }) 89 | }); 90 | 91 | history::history_destroy_c(hist); 92 | } 93 | { 94 | let mut hist = history::History::new(*histogram, 100_000); 95 | 96 | for e in black_box(&energies) { 97 | hist.add(*e); 98 | } 99 | 100 | group.bench_function("Rust", |b| { 101 | b.iter(|| { 102 | black_box(hist.relative_threshold()); 103 | }) 104 | }); 105 | } 106 | group.finish(); 107 | 108 | let mut group = c.benchmark_group(format!("history loudness range: 1M {name}")); 109 | #[cfg(feature = "c-tests")] 110 | unsafe { 111 | let hist = history::history_create_c(i32::from(*histogram), 100_000); 112 | 113 | for e in black_box(&energies) { 114 | history::history_add_c(hist, *e); 115 | } 116 | 117 | group.bench_function("C", |b| { 118 | b.iter(|| { 119 | black_box(history::history_loudness_range_c(hist)); 120 | }) 121 | }); 122 | 123 | history::history_destroy_c(hist); 124 | } 125 | { 126 | let mut hist = history::History::new(*histogram, 100_000); 127 | 128 | for e in black_box(&energies) { 129 | hist.add(*e); 130 | } 131 | 132 | group.bench_function("Rust", |b| { 133 | b.iter(|| { 134 | black_box(hist.loudness_range()); 135 | }) 136 | }); 137 | } 138 | group.finish(); 139 | } 140 | } 141 | 142 | criterion_group!(benches, criterion_benchmark); 143 | criterion_main!(benches); 144 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), 6 | specifically the [variant used by Rust](http://doc.crates.io/manifest.html#the-version-field). 7 | 8 | ## [Unreleased] - TBD 9 | 10 | ## [0.1.10] - 2024-10-26 11 | ### Added 12 | - Make `energy_to_loudness()` and `EbuR128::gating_block_count_and_energy()` 13 | public. 14 | - Add example that shows basic usage for loudness normalization and another 15 | example that calculates ReplayGain 2.0 track and album again. 16 | 17 | ## [0.1.9] - 2024-06-26 18 | ### Fixed 19 | - Avoid panic and non-sensical results if invalid (NaN or infinity) samples 20 | are provided and also enfore this behaviour with tests. 21 | 22 | ### Changed 23 | - Document Rust 1.60 as MSRV and check for this on the CI. 24 | 25 | ## [0.1.8] - 2023-04-27 26 | ### Fixed 27 | - Revert usage of `slice::copy_within()` as this causes a 20-35% slowdown due 28 | to the compiler not being able to optimize the code as much as before. 29 | - Use correct pkg-config filename. 30 | 31 | ### Changed 32 | - Sync C header version with the C implementation. 33 | - Fix various new clippy warnings. 34 | - Use const generics to clean up some code. 35 | 36 | ## [0.1.7] - 2022-10-28 37 | ### Changed 38 | - Various internal refactors / cleanups. 39 | - Updated dependencies. 40 | 41 | ## [0.1.6] - 2021-07-13 42 | ### Fixed 43 | - Flush-to-zero implementation was refactored to prevent the possibility to 44 | pass it to `mem::forget()` and thus leading to UB. This was not a problem in 45 | this codebase. 46 | 47 | ### Added 48 | - Specialized, optimized implementation for common interpolations. This speeds 49 | up the resampling/interpolation used by the true peak detection for mono, 50 | stereo and four channels by a factor of 2-5x. 51 | - Buffer-less true peak scanning instead of the previous implementation that 52 | goes via a temporary buffer. This speeds up true peak detection even more. 53 | - Add seeding API for supporting chunked analysis, i.e. analyzing a bigger 54 | input split into separate chunks in parallel. 55 | 56 | ### Changed 57 | - Various refactoring. 58 | - `unsafe` code was removed from the histogram handling without a measurable 59 | impact on the performance. 60 | - Use the `dasp` crates instead of the homegrown sample/frame abstraction. 61 | 62 | ## [0.1.5] - 2020-09-07 63 | ### Fixed 64 | - Allow only a single channel when setting `DualMono` also in 65 | `EbuR128::set_channel_map()`. The same constraint was already checked in 66 | `set_channel()`. 67 | - Chunk size in some of the reference tests was changed as it was too big. 68 | This fixed the remaining two reference tests that were failing before. 69 | 70 | ### Added 71 | - `EbuR128::reset()` to reset all state without reallocation. 72 | - Support for planar/non-interleaved inputs. 73 | 74 | ### Changed 75 | - Sample peak measurement was changed slightly and is now faster than the C 76 | implementation while still giving the same results. 77 | - Use `SmallVec` for temporary vecs in `EbuR128::loudness_global_multiple()` 78 | and `loudness_range_multiple()` to prevent heap allocations, and also use it 79 | in the filter of the interpolator, which slightly speeds it up. 80 | - CI was switched from Travis to GitHub actions and greatly improved. 81 | 82 | ## [0.1.4] - 2020-08-31 83 | ### Fixed 84 | - Fix compiler warning about unused use statement in benchmarks 85 | - Fix various spelling errors in code comments 86 | - Protect various calculations from overflows when creating a new instance or 87 | changing settings so that instead of panicking we return a proper error 88 | - Use `std::f64::INFINITY` and similar instead of `f64::INFINITY` to fix 89 | compilation with older Rust versions. Version up to Rust 1.32.0 are 90 | supported now. 91 | 92 | ### Changed 93 | - Replaced various `Vec` with `Box<[T]>` to make it clearer that these are 94 | never changing their size after the initial allocation, and to also get rid 95 | of the additional unused storage of the capacity. 96 | 97 | ### Added 98 | - More documentation for internal types 99 | 100 | ## [0.1.3] - 2020-08-28 101 | ### Fixed 102 | - Return `-f64::INFINITY` instead of `f64::MIN` for values below the 103 | thresholds from various functions. This matches the behaviour of the C 104 | library and generally makes more sense. 105 | 106 | ## [0.1.2] - 2020-08-28 107 | ### Changed 108 | - Port libebur128 C code to Rust. This is a pure Rust implementation of it now 109 | and the only remaining C code is in the tests for comparing the two 110 | implementations. 111 | 112 | ## [0.1.1] - 2020-04-14 113 | ### Fixed 114 | - Fix build with the MSVC toolchain. 115 | 116 | ## 0.1.0 - 2020-01-06 117 | - Initial release of ebur128. 118 | 119 | [Unreleased]: https://github.com/sdroege/ebur128/compare/0.1.10...HEAD 120 | [0.1.10]: https://github.com/sdroege/ebur128/compare/0.1.9...0.1.10 121 | [0.1.9]: https://github.com/sdroege/ebur128/compare/0.1.8...0.1.9 122 | [0.1.8]: https://github.com/sdroege/ebur128/compare/0.1.7...0.1.8 123 | [0.1.7]: https://github.com/sdroege/ebur128/compare/0.1.6...0.1.7 124 | [0.1.6]: https://github.com/sdroege/ebur128/compare/0.1.5...0.1.6 125 | [0.1.5]: https://github.com/sdroege/ebur128/compare/0.1.4...0.1.5 126 | [0.1.4]: https://github.com/sdroege/ebur128/compare/0.1.3...0.1.4 127 | [0.1.3]: https://github.com/sdroege/ebur128/compare/0.1.2...0.1.3 128 | [0.1.2]: https://github.com/sdroege/ebur128/compare/0.1.1...0.1.2 129 | [0.1.1]: https://github.com/sdroege/ebur128/compare/0.1.0...0.1.1 130 | -------------------------------------------------------------------------------- /tests/c/interp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define _USE_MATH_DEFINES 4 | #include 5 | #include 6 | #include 7 | 8 | #define CHECK_ERROR(condition, errorcode, goto_point) \ 9 | if ((condition)) { \ 10 | errcode = (errorcode); \ 11 | goto goto_point; \ 12 | } 13 | 14 | #define ALMOST_ZERO 0.000001 15 | 16 | typedef struct { 17 | unsigned int count; /* Number of coefficients in this subfilter */ 18 | unsigned int* index; /* Delay index of corresponding filter coeff */ 19 | double* coeff; /* List of subfilter coefficients */ 20 | } interp_filter; 21 | 22 | typedef struct { /* Data structure for polyphase FIR interpolator */ 23 | unsigned int factor; /* Interpolation factor of the interpolator */ 24 | unsigned int taps; /* Taps (prefer odd to increase zero coeffs) */ 25 | unsigned int channels; /* Number of channels */ 26 | unsigned int delay; /* Size of delay buffer */ 27 | interp_filter* filter; /* List of subfilters (one for each factor) */ 28 | float** z; /* List of delay buffers (one for each channel) */ 29 | unsigned int zi; /* Current delay buffer index */ 30 | } interpolator_c; 31 | 32 | interpolator_c* 33 | interp_create_c(unsigned int taps, unsigned int factor, unsigned int channels) { 34 | int errcode; /* unused */ 35 | interpolator_c* interp; 36 | unsigned int j; 37 | 38 | (void) errcode; 39 | 40 | interp = (interpolator_c*) calloc(1, sizeof(interpolator_c)); 41 | CHECK_ERROR(!interp, 0, exit); 42 | 43 | interp->taps = taps; 44 | interp->factor = factor; 45 | interp->channels = channels; 46 | interp->delay = (interp->taps + interp->factor - 1) / interp->factor; 47 | 48 | /* Initialize the filter memory 49 | * One subfilter per interpolation factor. */ 50 | interp->filter = 51 | (interp_filter*) calloc(interp->factor, sizeof(*interp->filter)); 52 | CHECK_ERROR(!interp->filter, 0, free_interp); 53 | 54 | for (j = 0; j < interp->factor; j++) { 55 | interp->filter[j].index = 56 | (unsigned int*) calloc(interp->delay, sizeof(unsigned int)); 57 | interp->filter[j].coeff = (double*) calloc(interp->delay, sizeof(double)); 58 | CHECK_ERROR(!interp->filter[j].index || !interp->filter[j].coeff, 0, 59 | free_filter_index_coeff); 60 | } 61 | 62 | /* One delay buffer per channel. */ 63 | interp->z = (float**) calloc(interp->channels, sizeof(float*)); 64 | CHECK_ERROR(!interp->z, 0, free_filter_index_coeff); 65 | for (j = 0; j < interp->channels; j++) { 66 | interp->z[j] = (float*) calloc(interp->delay, sizeof(float)); 67 | CHECK_ERROR(!interp->z[j], 0, free_filter_z); 68 | } 69 | 70 | /* Calculate the filter coefficients */ 71 | for (j = 0; j < interp->taps; j++) { 72 | /* Calculate sinc */ 73 | double m = (double) j - (double) (interp->taps - 1) / 2.0; 74 | double c = 1.0; 75 | if (fabs(m) > ALMOST_ZERO) { 76 | c = sin(m * M_PI / interp->factor) / (m * M_PI / interp->factor); 77 | } 78 | /* Apply Hanning window */ 79 | c *= 0.5 * (1 - cos(2 * M_PI * j / (interp->taps - 1))); 80 | 81 | if (fabs(c) > ALMOST_ZERO) { /* Ignore any zero coeffs. */ 82 | /* Put the coefficient into the correct subfilter */ 83 | unsigned int f = j % interp->factor; 84 | unsigned int t = interp->filter[f].count++; 85 | interp->filter[f].coeff[t] = c; 86 | interp->filter[f].index[t] = j / interp->factor; 87 | } 88 | } 89 | return interp; 90 | 91 | free_filter_z: 92 | for (j = 0; j < interp->channels; j++) { 93 | free(interp->z[j]); 94 | } 95 | free(interp->z); 96 | free_filter_index_coeff: 97 | for (j = 0; j < interp->factor; j++) { 98 | free(interp->filter[j].index); 99 | free(interp->filter[j].coeff); 100 | } 101 | free(interp->filter); 102 | free_interp: 103 | free(interp); 104 | exit: 105 | return NULL; 106 | } 107 | 108 | void interp_destroy_c(interpolator_c* interp) { 109 | unsigned int j = 0; 110 | if (!interp) { 111 | return; 112 | } 113 | for (j = 0; j < interp->factor; j++) { 114 | free(interp->filter[j].index); 115 | free(interp->filter[j].coeff); 116 | } 117 | free(interp->filter); 118 | for (j = 0; j < interp->channels; j++) { 119 | free(interp->z[j]); 120 | } 121 | free(interp->z); 122 | free(interp); 123 | } 124 | 125 | size_t 126 | interp_process_c(interpolator_c* interp, size_t frames, float* in, float* out) { 127 | size_t frame = 0; 128 | unsigned int chan = 0; 129 | unsigned int f = 0; 130 | unsigned int t = 0; 131 | unsigned int out_stride = interp->channels * interp->factor; 132 | float* outp = 0; 133 | double acc = 0; 134 | double c = 0; 135 | 136 | for (frame = 0; frame < frames; frame++) { 137 | for (chan = 0; chan < interp->channels; chan++) { 138 | /* Add sample to delay buffer */ 139 | interp->z[chan][interp->zi] = *in++; 140 | /* Apply coefficients */ 141 | outp = out + chan; 142 | for (f = 0; f < interp->factor; f++) { 143 | acc = 0.0; 144 | for (t = 0; t < interp->filter[f].count; t++) { 145 | int i = (int) interp->zi - (int) interp->filter[f].index[t]; 146 | if (i < 0) { 147 | i += (int) interp->delay; 148 | } 149 | c = interp->filter[f].coeff[t]; 150 | acc += (double) interp->z[chan][i] * c; 151 | } 152 | *outp = (float) acc; 153 | outp += interp->channels; 154 | } 155 | } 156 | out += out_stride; 157 | interp->zi++; 158 | if (interp->zi == interp->delay) { 159 | interp->zi = 0; 160 | } 161 | } 162 | 163 | return frames * interp->factor; 164 | } 165 | -------------------------------------------------------------------------------- /benches/true_peak.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use ebur128::true_peak; 4 | 5 | pub fn criterion_benchmark(c: &mut Criterion) { 6 | let mut data = vec![0i16; 19200 * 2]; 7 | let mut data_planar = vec![0i16; 19200 * 2]; 8 | let (fst, snd) = data_planar.split_at_mut(19200); 9 | let mut accumulator = 0.0; 10 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 11 | for (out, (fst, snd)) in Iterator::zip( 12 | data.chunks_exact_mut(2), 13 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 14 | ) { 15 | let val = f32::sin(accumulator) * i16::MAX as f32; 16 | out[0] = val as i16; 17 | out[1] = val as i16; 18 | *fst = val as i16; 19 | *snd = val as i16; 20 | accumulator += step; 21 | } 22 | 23 | let mut peaks = vec![0.0f64; 2]; 24 | 25 | let mut group = c.benchmark_group("true_peak: 48kHz 2ch i16"); 26 | 27 | #[cfg(feature = "c-tests")] 28 | unsafe { 29 | let tp = true_peak::true_peak_create_c(black_box(48_000), black_box(2)); 30 | 31 | group.bench_function("C", |b| { 32 | b.iter(|| { 33 | true_peak::true_peak_check_short_c( 34 | black_box(tp), 35 | black_box(data.len() / 2usize), 36 | black_box(data.as_ptr()), 37 | black_box(peaks.as_mut_ptr()), 38 | ); 39 | }) 40 | }); 41 | 42 | true_peak::true_peak_destroy_c(tp); 43 | } 44 | 45 | { 46 | let mut tp = true_peak::TruePeak::new(black_box(48_000), black_box(2)).unwrap(); 47 | group.bench_function("Rust/Interleaved", |b| { 48 | b.iter(|| { 49 | tp.check_true_peak( 50 | black_box(ebur128::Interleaved::new(&data, 2).unwrap()), 51 | black_box(&mut peaks), 52 | ); 53 | }) 54 | }); 55 | 56 | let mut tp = true_peak::TruePeak::new(black_box(48_000), black_box(2)).unwrap(); 57 | group.bench_function("Rust/Planar", |b| { 58 | b.iter(|| { 59 | tp.check_true_peak( 60 | black_box(ebur128::Planar::new(&[fst, snd]).unwrap()), 61 | black_box(&mut peaks), 62 | ); 63 | }) 64 | }); 65 | } 66 | 67 | group.finish(); 68 | 69 | let mut data = vec![0i32; 19200 * 2]; 70 | let mut data_planar = vec![0i32; 19200 * 2]; 71 | let (fst, snd) = data_planar.split_at_mut(19200); 72 | let mut accumulator = 0.0; 73 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 74 | for (out, (fst, snd)) in Iterator::zip( 75 | data.chunks_exact_mut(2), 76 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 77 | ) { 78 | let val = f32::sin(accumulator) * i32::MAX as f32; 79 | out[0] = val as i32; 80 | out[1] = val as i32; 81 | *fst = val as i32; 82 | *snd = val as i32; 83 | accumulator += step; 84 | } 85 | 86 | let mut group = c.benchmark_group("true_peak: 48kHz 2ch i32"); 87 | 88 | #[cfg(feature = "c-tests")] 89 | unsafe { 90 | let tp = true_peak::true_peak_create_c(black_box(48_000), black_box(2)); 91 | 92 | group.bench_function("C", |b| { 93 | b.iter(|| { 94 | true_peak::true_peak_check_int_c( 95 | black_box(tp), 96 | black_box(data.len() / 2usize), 97 | black_box(data.as_ptr()), 98 | black_box(peaks.as_mut_ptr()), 99 | ); 100 | }) 101 | }); 102 | 103 | true_peak::true_peak_destroy_c(tp); 104 | } 105 | 106 | { 107 | let mut tp = true_peak::TruePeak::new(black_box(48_000), black_box(2)).unwrap(); 108 | group.bench_function("Rust/Interleaved", |b| { 109 | b.iter(|| { 110 | tp.check_true_peak( 111 | black_box(ebur128::Interleaved::new(&data, 2).unwrap()), 112 | black_box(&mut peaks), 113 | ); 114 | }) 115 | }); 116 | 117 | let mut tp = true_peak::TruePeak::new(black_box(48_000), black_box(2)).unwrap(); 118 | group.bench_function("Rust/Planar", |b| { 119 | b.iter(|| { 120 | tp.check_true_peak( 121 | black_box(ebur128::Planar::new(&[fst, snd]).unwrap()), 122 | black_box(&mut peaks), 123 | ); 124 | }) 125 | }); 126 | } 127 | 128 | group.finish(); 129 | 130 | let mut data = vec![0.0f32; 19200 * 2]; 131 | let mut data_planar = vec![0.0f32; 19200 * 2]; 132 | let (fst, snd) = data_planar.split_at_mut(19200); 133 | let mut accumulator = 0.0; 134 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 135 | for (out, (fst, snd)) in Iterator::zip( 136 | data.chunks_exact_mut(2), 137 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 138 | ) { 139 | let val = f32::sin(accumulator); 140 | out[0] = val; 141 | out[1] = val; 142 | *fst = val; 143 | *snd = val; 144 | accumulator += step; 145 | } 146 | 147 | let mut group = c.benchmark_group("true_peak: 48kHz 2ch f32"); 148 | 149 | #[cfg(feature = "c-tests")] 150 | unsafe { 151 | let tp = true_peak::true_peak_create_c(black_box(48_000), black_box(2)); 152 | 153 | group.bench_function("C", |b| { 154 | b.iter(|| { 155 | true_peak::true_peak_check_float_c( 156 | black_box(tp), 157 | black_box(data.len() / 2usize), 158 | black_box(data.as_ptr()), 159 | black_box(peaks.as_mut_ptr()), 160 | ); 161 | }) 162 | }); 163 | 164 | true_peak::true_peak_destroy_c(tp); 165 | } 166 | 167 | { 168 | let mut tp = true_peak::TruePeak::new(black_box(48_000), black_box(2)).unwrap(); 169 | group.bench_function("Rust/Interleaved", |b| { 170 | b.iter(|| { 171 | tp.check_true_peak( 172 | black_box(ebur128::Interleaved::new(&data, 2).unwrap()), 173 | black_box(&mut peaks), 174 | ); 175 | }) 176 | }); 177 | 178 | let mut tp = true_peak::TruePeak::new(black_box(48_000), black_box(2)).unwrap(); 179 | group.bench_function("Rust/Planar", |b| { 180 | b.iter(|| { 181 | tp.check_true_peak( 182 | black_box(ebur128::Planar::new(&[fst, snd]).unwrap()), 183 | black_box(&mut peaks), 184 | ); 185 | }) 186 | }); 187 | } 188 | 189 | group.finish(); 190 | 191 | let mut data = vec![0.0f64; 19200 * 2]; 192 | let mut data_planar = vec![0.0f64; 19200 * 2]; 193 | let (fst, snd) = data_planar.split_at_mut(19200); 194 | let mut accumulator = 0.0; 195 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 196 | for (out, (fst, snd)) in Iterator::zip( 197 | data.chunks_exact_mut(2), 198 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 199 | ) { 200 | let val = f32::sin(accumulator); 201 | out[0] = val as f64; 202 | out[1] = val as f64; 203 | *fst = val as f64; 204 | *snd = val as f64; 205 | accumulator += step; 206 | } 207 | 208 | let mut group = c.benchmark_group("true_peak: 48kHz 2ch f64"); 209 | 210 | #[cfg(feature = "c-tests")] 211 | unsafe { 212 | let tp = true_peak::true_peak_create_c(black_box(48_000), black_box(2)); 213 | 214 | group.bench_function("C", |b| { 215 | b.iter(|| { 216 | true_peak::true_peak_check_double_c( 217 | black_box(tp), 218 | black_box(data.len() / 2usize), 219 | black_box(data.as_ptr()), 220 | black_box(peaks.as_mut_ptr()), 221 | ); 222 | }) 223 | }); 224 | 225 | true_peak::true_peak_destroy_c(tp); 226 | } 227 | 228 | { 229 | let mut tp = true_peak::TruePeak::new(black_box(48_000), black_box(2)).unwrap(); 230 | group.bench_function("Rust/Interleaved", |b| { 231 | b.iter(|| { 232 | tp.check_true_peak( 233 | black_box(ebur128::Interleaved::new(&data, 2).unwrap()), 234 | black_box(&mut peaks), 235 | ); 236 | }) 237 | }); 238 | 239 | let mut tp = true_peak::TruePeak::new(black_box(48_000), black_box(2)).unwrap(); 240 | group.bench_function("Rust/Planar", |b| { 241 | b.iter(|| { 242 | tp.check_true_peak( 243 | black_box(ebur128::Planar::new(&[fst, snd]).unwrap()), 244 | black_box(&mut peaks), 245 | ); 246 | }) 247 | }); 248 | } 249 | 250 | group.finish(); 251 | } 252 | 253 | criterion_group!(benches, criterion_benchmark); 254 | criterion_main!(benches); 255 | -------------------------------------------------------------------------------- /tests/c/filter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define _USE_MATH_DEFINES 4 | #include 5 | #include 6 | #include 7 | 8 | typedef void * true_peak; 9 | extern true_peak* true_peak_create_c(unsigned int rate, unsigned int channels); 10 | extern void true_peak_destroy_c(true_peak* tp); 11 | extern void true_peak_check_short_c(true_peak* tp, size_t frames, const int16_t* src, double* peaks); 12 | extern void true_peak_check_int_c(true_peak* tp, size_t frames, const int32_t* src, double* peaks); 13 | extern void true_peak_check_float_c(true_peak* tp, size_t frames, const float* src, double* peaks); 14 | extern void true_peak_check_double_c(true_peak* tp, size_t frames, const double* src, double* peaks); 15 | 16 | #define FILTER_STATE_SIZE 5 17 | #define EBUR128_UNUSED 0 18 | 19 | #define EBUR128_MAX(a, b) (((a) > (b)) ? (a) : (b)) 20 | 21 | /** BS.1770 filter state. */ 22 | typedef double filter_state[FILTER_STATE_SIZE]; 23 | 24 | typedef struct { 25 | unsigned int rate; 26 | unsigned int channels; 27 | 28 | /** BS.1770 filter coefficients (numerator). */ 29 | double b[5]; 30 | /** BS.1770 filter coefficients (denominator). */ 31 | double a[5]; 32 | /** one filter_state per channel. */ 33 | filter_state* v; 34 | 35 | /** Maximum sample peak, one per channel */ 36 | int calculate_sample_peak; 37 | double* sample_peak; 38 | /** Maximum true peak, one per channel */ 39 | true_peak *tp; 40 | double* true_peak; 41 | } filter; 42 | 43 | filter * filter_create_c(unsigned int rate, unsigned int channels, int calculate_sample_peak, int calculate_true_peak) { 44 | filter* f = (filter*) calloc(1, sizeof(filter)); 45 | int i, j; 46 | 47 | f->rate = rate; 48 | f->channels = channels; 49 | 50 | double f0 = 1681.974450955533; 51 | double G = 3.999843853973347; 52 | double Q = 0.7071752369554196; 53 | 54 | double K = tan(M_PI * f0 / (double) f->rate); 55 | double Vh = pow(10.0, G / 20.0); 56 | double Vb = pow(Vh, 0.4996667741545416); 57 | 58 | double pb[3] = { 0.0, 0.0, 0.0 }; 59 | double pa[3] = { 1.0, 0.0, 0.0 }; 60 | double rb[3] = { 1.0, -2.0, 1.0 }; 61 | double ra[3] = { 1.0, 0.0, 0.0 }; 62 | 63 | double a0 = 1.0 + K / Q + K * K; 64 | pb[0] = (Vh + Vb * K / Q + K * K) / a0; 65 | pb[1] = 2.0 * (K * K - Vh) / a0; 66 | pb[2] = (Vh - Vb * K / Q + K * K) / a0; 67 | pa[1] = 2.0 * (K * K - 1.0) / a0; 68 | pa[2] = (1.0 - K / Q + K * K) / a0; 69 | 70 | /* fprintf(stderr, "%.14f %.14f %.14f %.14f %.14f\n", 71 | b1[0], b1[1], b1[2], a1[1], a1[2]); */ 72 | 73 | f0 = 38.13547087602444; 74 | Q = 0.5003270373238773; 75 | K = tan(M_PI * f0 / (double) f->rate); 76 | 77 | ra[1] = 2.0 * (K * K - 1.0) / (1.0 + K / Q + K * K); 78 | ra[2] = (1.0 - K / Q + K * K) / (1.0 + K / Q + K * K); 79 | 80 | /* fprintf(stderr, "%.14f %.14f\n", a2[1], a2[2]); */ 81 | 82 | f->b[0] = pb[0] * rb[0]; 83 | f->b[1] = pb[0] * rb[1] + pb[1] * rb[0]; 84 | f->b[2] = pb[0] * rb[2] + pb[1] * rb[1] + pb[2] * rb[0]; 85 | f->b[3] = pb[1] * rb[2] + pb[2] * rb[1]; 86 | f->b[4] = pb[2] * rb[2]; 87 | 88 | f->a[0] = pa[0] * ra[0]; 89 | f->a[1] = pa[0] * ra[1] + pa[1] * ra[0]; 90 | f->a[2] = pa[0] * ra[2] + pa[1] * ra[1] + pa[2] * ra[0]; 91 | f->a[3] = pa[1] * ra[2] + pa[2] * ra[1]; 92 | f->a[4] = pa[2] * ra[2]; 93 | 94 | f->v = (filter_state*) malloc(channels * sizeof(filter_state)); 95 | for (i = 0; i < (int) channels; ++i) { 96 | for (j = 0; j < FILTER_STATE_SIZE; ++j) { 97 | f->v[i][j] = 0.0; 98 | } 99 | } 100 | 101 | f->calculate_sample_peak = calculate_sample_peak; 102 | f->sample_peak = (double*) malloc(channels * sizeof(double)); 103 | 104 | f->tp = calculate_true_peak ? true_peak_create_c(rate, channels) : NULL; 105 | f->true_peak = (double*) malloc(channels * sizeof(double)); 106 | 107 | for (i = 0; i < (int) channels; ++i) { 108 | f->sample_peak[i] = 0.0; 109 | f->true_peak[i] = 0.0; 110 | } 111 | 112 | return f; 113 | } 114 | 115 | void filter_destroy_c(filter* f) { 116 | free(f->sample_peak); 117 | true_peak_destroy_c(f->tp); 118 | free(f->true_peak); 119 | free(f); 120 | } 121 | 122 | const double* filter_sample_peak_c(const filter *f) { 123 | return f->sample_peak; 124 | } 125 | 126 | const double* filter_true_peak_c(const filter *f) { 127 | return f->true_peak; 128 | } 129 | 130 | void filter_reset_peaks_c(filter *f) { 131 | int i; 132 | 133 | for (i = 0; i < (int) f->channels; ++i) { 134 | f->sample_peak[i] = 0.0; 135 | f->true_peak[i] = 0.0; 136 | } 137 | } 138 | 139 | #if defined(__SSE2_MATH__) || defined(_M_X64) || _M_IX86_FP >= 2 140 | #include 141 | #define TURN_ON_FTZ \ 142 | unsigned int mxcsr = _mm_getcsr(); \ 143 | _mm_setcsr(mxcsr | _MM_FLUSH_ZERO_ON); 144 | #define TURN_OFF_FTZ _mm_setcsr(mxcsr); 145 | #define FLUSH_MANUALLY 146 | #else 147 | #warning "manual FTZ is being used, please enable SSE2 (-msse2 -mfpmath=sse)" 148 | #define TURN_ON_FTZ 149 | #define TURN_OFF_FTZ 150 | #define FLUSH_MANUALLY \ 151 | f->v[c][4] = fabs(f->v[c][4]) < DBL_MIN ? 0.0 : f->v[c][4]; \ 152 | f->v[c][3] = fabs(f->v[c][3]) < DBL_MIN ? 0.0 : f->v[c][3]; \ 153 | f->v[c][2] = fabs(f->v[c][2]) < DBL_MIN ? 0.0 : f->v[c][2]; \ 154 | f->v[c][1] = fabs(f->v[c][1]) < DBL_MIN ? 0.0 : f->v[c][1]; 155 | #endif 156 | 157 | #define EBUR128_FILTER(type, min_scale, max_scale) \ 158 | void filter_process_##type##_c(filter* f, size_t frames, const type* src, \ 159 | double* audio_data, const unsigned int* channel_map) { \ 160 | static double scaling_factor = \ 161 | EBUR128_MAX(-((double) (min_scale)), (double) (max_scale)); \ 162 | \ 163 | size_t i, c; \ 164 | \ 165 | TURN_ON_FTZ \ 166 | \ 167 | if (f->calculate_sample_peak) { \ 168 | for (c = 0; c < f->channels; ++c) { \ 169 | double max = 0.0; \ 170 | for (i = 0; i < frames; ++i) { \ 171 | double cur = (double) src[i * f->channels + c]; \ 172 | if (EBUR128_MAX(cur, -cur) > max) { \ 173 | max = EBUR128_MAX(cur, -cur); \ 174 | } \ 175 | } \ 176 | max /= scaling_factor; \ 177 | if (max > f->sample_peak[c]) { \ 178 | f->sample_peak[c] = max; \ 179 | } \ 180 | } \ 181 | } \ 182 | if (f->tp) { \ 183 | true_peak_check_##type##_c(f->tp, frames, src, f->true_peak); \ 184 | } \ 185 | for (c = 0; c < f->channels; ++c) { \ 186 | if (channel_map[c] == EBUR128_UNUSED) { \ 187 | continue; \ 188 | } \ 189 | for (i = 0; i < frames; ++i) { \ 190 | f->v[c][0] = \ 191 | (double) ((double) src[i * f->channels + c] / scaling_factor) - \ 192 | f->a[1] * f->v[c][1] - /**/ \ 193 | f->a[2] * f->v[c][2] - /**/ \ 194 | f->a[3] * f->v[c][3] - /**/ \ 195 | f->a[4] * f->v[c][4]; \ 196 | audio_data[i * f->channels + c] = /**/ \ 197 | f->b[0] * f->v[c][0] + /**/ \ 198 | f->b[1] * f->v[c][1] + /**/ \ 199 | f->b[2] * f->v[c][2] + /**/ \ 200 | f->b[3] * f->v[c][3] + /**/ \ 201 | f->b[4] * f->v[c][4]; \ 202 | f->v[c][4] = f->v[c][3]; \ 203 | f->v[c][3] = f->v[c][2]; \ 204 | f->v[c][2] = f->v[c][1]; \ 205 | f->v[c][1] = f->v[c][0]; \ 206 | } \ 207 | FLUSH_MANUALLY \ 208 | } \ 209 | TURN_OFF_FTZ \ 210 | } 211 | 212 | EBUR128_FILTER(short, SHRT_MIN, SHRT_MAX) 213 | EBUR128_FILTER(int, INT_MIN, INT_MAX) 214 | EBUR128_FILTER(float, -1.0f, 1.0f) 215 | EBUR128_FILTER(double, -1.0, 1.0) 216 | -------------------------------------------------------------------------------- /tests/c/history.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define _USE_MATH_DEFINES 4 | #include 5 | #include 6 | #include 7 | 8 | #include "queue.h" 9 | 10 | STAILQ_HEAD(ebur128_double_queue, ebur128_dq_entry); 11 | struct ebur128_dq_entry { 12 | double z; 13 | STAILQ_ENTRY(ebur128_dq_entry) entries; 14 | }; 15 | 16 | static double minus_twenty_decibels; 17 | static double relative_gate = -10.0; 18 | static double relative_gate_factor; 19 | static double histogram_energies[1000]; 20 | static double histogram_energy_boundaries[1001]; 21 | 22 | typedef struct { 23 | int use_histogram; 24 | 25 | struct ebur128_double_queue block_list; 26 | unsigned long block_list_max; 27 | unsigned long block_list_size; 28 | 29 | unsigned long* block_energy_histogram; 30 | } history; 31 | 32 | static double ebur128_energy_to_loudness(double energy) { 33 | return 10 * (log(energy) / log(10.0)) - 0.691; 34 | } 35 | 36 | static size_t find_histogram_index(double energy) { 37 | size_t index_min = 0; 38 | size_t index_max = 1000; 39 | size_t index_mid; 40 | 41 | do { 42 | index_mid = (index_min + index_max) / 2; 43 | if (energy >= histogram_energy_boundaries[index_mid]) { 44 | index_min = index_mid; 45 | } else { 46 | index_max = index_mid; 47 | } 48 | } while (index_max - index_min != 1); 49 | 50 | return index_min; 51 | } 52 | 53 | void history_init_c(void) { 54 | int i; 55 | 56 | relative_gate_factor = pow(10.0, relative_gate / 10.0); 57 | minus_twenty_decibels = pow(10.0, -20.0 / 10.0); 58 | 59 | histogram_energy_boundaries[0] = pow(10.0, (-70.0 + 0.691) / 10.0); 60 | for (i = 0; i < 1000; ++i) { 61 | histogram_energies[i] = 62 | pow(10.0, ((double) i / 10.0 - 69.95 + 0.691) / 10.0); 63 | } 64 | for (i = 1; i < 1001; ++i) { 65 | histogram_energy_boundaries[i] = 66 | pow(10.0, ((double) i / 10.0 - 70.0 + 0.691) / 10.0); 67 | } 68 | } 69 | 70 | history* history_create_c(int use_histogram, size_t max) { 71 | int i; 72 | history *hist = calloc(1, sizeof(history)); 73 | 74 | hist->use_histogram = use_histogram; 75 | 76 | if (hist->use_histogram) { 77 | hist->block_energy_histogram = 78 | (unsigned long*) malloc(1000 * sizeof(unsigned long)); 79 | for (i = 0; i < 1000; ++i) { 80 | hist->block_energy_histogram[i] = 0; 81 | } 82 | } else { 83 | hist->block_energy_histogram = NULL; 84 | } 85 | 86 | STAILQ_INIT(&hist->block_list); 87 | hist->block_list_size = 0; 88 | hist->block_list_max = max; 89 | 90 | return hist; 91 | } 92 | 93 | void history_add_c(history* hist, double energy) { 94 | if (energy >= histogram_energy_boundaries[0]) { 95 | if (hist->use_histogram) { 96 | ++hist->block_energy_histogram[find_histogram_index(energy)]; 97 | } else { 98 | struct ebur128_dq_entry* block; 99 | if (hist->block_list_size == hist->block_list_max) { 100 | block = STAILQ_FIRST(&hist->block_list); 101 | STAILQ_REMOVE_HEAD(&hist->block_list, entries); 102 | } else { 103 | block = 104 | (struct ebur128_dq_entry*) malloc(sizeof(struct ebur128_dq_entry)); 105 | hist->block_list_size++; 106 | } 107 | block->z = energy; 108 | STAILQ_INSERT_TAIL(&hist->block_list, block, entries); 109 | } 110 | } 111 | } 112 | 113 | void history_set_max_size_c(history* hist, size_t max) { 114 | hist->block_list_max = max; 115 | while (hist->block_list_size > hist->block_list_max) { 116 | struct ebur128_dq_entry* block = STAILQ_FIRST(&hist->block_list); 117 | STAILQ_REMOVE_HEAD(&hist->block_list, entries); 118 | free(block); 119 | hist->block_list_size--; 120 | } 121 | } 122 | 123 | static void history_calc_relative_threshold(const history* hist, size_t* above_thresh_counter, double* relative_threshold) { 124 | struct ebur128_dq_entry* it; 125 | size_t i; 126 | 127 | if (hist->use_histogram) { 128 | for (i = 0; i < 1000; ++i) { 129 | *relative_threshold += 130 | hist->block_energy_histogram[i] * histogram_energies[i]; 131 | *above_thresh_counter += hist->block_energy_histogram[i]; 132 | } 133 | } else { 134 | STAILQ_FOREACH(it, &hist->block_list, entries) { 135 | ++*above_thresh_counter; 136 | *relative_threshold += it->z; 137 | } 138 | } 139 | } 140 | 141 | double history_gated_loudness_c(const history* hist) { 142 | struct ebur128_dq_entry* it; 143 | double gated_loudness = 0.0; 144 | double relative_threshold = 0.0; 145 | size_t above_thresh_counter = 0; 146 | size_t j, start_index; 147 | 148 | history_calc_relative_threshold(hist, &above_thresh_counter, &relative_threshold); 149 | if (!above_thresh_counter) { 150 | return -HUGE_VAL; 151 | } 152 | 153 | relative_threshold /= (double) above_thresh_counter; 154 | relative_threshold *= relative_gate_factor; 155 | 156 | above_thresh_counter = 0; 157 | if (relative_threshold < histogram_energy_boundaries[0]) { 158 | start_index = 0; 159 | } else { 160 | start_index = find_histogram_index(relative_threshold); 161 | if (relative_threshold > histogram_energies[start_index]) { 162 | ++start_index; 163 | } 164 | } 165 | 166 | if (hist->use_histogram) { 167 | for (j = start_index; j < 1000; ++j) { 168 | gated_loudness += 169 | hist->block_energy_histogram[j] * histogram_energies[j]; 170 | above_thresh_counter += hist->block_energy_histogram[j]; 171 | } 172 | } else { 173 | STAILQ_FOREACH(it, &hist->block_list, entries) { 174 | if (it->z >= relative_threshold) { 175 | ++above_thresh_counter; 176 | gated_loudness += it->z; 177 | } 178 | } 179 | } 180 | 181 | if (!above_thresh_counter) { 182 | return -HUGE_VAL; 183 | } 184 | gated_loudness /= (double) above_thresh_counter; 185 | 186 | return ebur128_energy_to_loudness(gated_loudness); 187 | } 188 | 189 | double history_relative_threshold_c(const history* hist) { 190 | double relative_threshold = 0.0; 191 | size_t above_thresh_counter = 0; 192 | 193 | history_calc_relative_threshold(hist, &above_thresh_counter, 194 | &relative_threshold); 195 | 196 | if (!above_thresh_counter) { 197 | return -70.0; 198 | } 199 | 200 | relative_threshold /= (double) above_thresh_counter; 201 | relative_threshold *= relative_gate_factor; 202 | 203 | return ebur128_energy_to_loudness(relative_threshold); 204 | } 205 | 206 | static int history_double_cmp(const void* p1, const void* p2) { 207 | const double* d1 = (const double*) p1; 208 | const double* d2 = (const double*) p2; 209 | return (*d1 > *d2) - (*d1 < *d2); 210 | } 211 | 212 | double history_loudness_range_c(const history* hist) { 213 | size_t j; 214 | struct ebur128_dq_entry* it; 215 | double* stl_vector; 216 | size_t stl_size; 217 | double* stl_relgated; 218 | size_t stl_relgated_size; 219 | double stl_power, stl_integrated; 220 | /* High and low percentile energy */ 221 | double h_en, l_en; 222 | 223 | if (hist->use_histogram) { 224 | unsigned long hist_[1000] = { 0 }; 225 | size_t percentile_low, percentile_high; 226 | size_t index; 227 | 228 | stl_size = 0; 229 | stl_power = 0.0; 230 | for (j = 0; j < 1000; ++j) { 231 | hist_[j] += hist->block_energy_histogram[j]; 232 | stl_size += hist->block_energy_histogram[j]; 233 | stl_power += hist->block_energy_histogram[j] * 234 | histogram_energies[j]; 235 | } 236 | if (!stl_size) { 237 | return 0.0; 238 | } 239 | 240 | stl_power /= stl_size; 241 | stl_integrated = minus_twenty_decibels * stl_power; 242 | 243 | if (stl_integrated < histogram_energy_boundaries[0]) { 244 | index = 0; 245 | } else { 246 | index = find_histogram_index(stl_integrated); 247 | if (stl_integrated > histogram_energies[index]) { 248 | ++index; 249 | } 250 | } 251 | stl_size = 0; 252 | for (j = index; j < 1000; ++j) { 253 | stl_size += hist_[j]; 254 | } 255 | if (!stl_size) { 256 | return 0.0; 257 | } 258 | 259 | percentile_low = (size_t)((stl_size - 1) * 0.1 + 0.5); 260 | percentile_high = (size_t)((stl_size - 1) * 0.95 + 0.5); 261 | 262 | stl_size = 0; 263 | j = index; 264 | while (stl_size <= percentile_low) { 265 | stl_size += hist_[j++]; 266 | } 267 | l_en = histogram_energies[j - 1]; 268 | while (stl_size <= percentile_high) { 269 | stl_size += hist_[j++]; 270 | } 271 | h_en = histogram_energies[j - 1]; 272 | 273 | return ebur128_energy_to_loudness(h_en) - ebur128_energy_to_loudness(l_en); 274 | } 275 | 276 | stl_size = 0; 277 | STAILQ_FOREACH(it, &hist->block_list, entries) { 278 | ++stl_size; 279 | } 280 | if (!stl_size) { 281 | return 0.0; 282 | } 283 | stl_vector = (double*) malloc(stl_size * sizeof(double)); 284 | 285 | j = 0; 286 | STAILQ_FOREACH(it, &hist->block_list, entries) { 287 | stl_vector[j] = it->z; 288 | ++j; 289 | } 290 | qsort(stl_vector, stl_size, sizeof(double), history_double_cmp); 291 | stl_power = 0.0; 292 | for (j = 0; j < stl_size; ++j) { 293 | stl_power += stl_vector[j]; 294 | } 295 | stl_power /= (double) stl_size; 296 | stl_integrated = minus_twenty_decibels * stl_power; 297 | 298 | stl_relgated = stl_vector; 299 | stl_relgated_size = stl_size; 300 | while (stl_relgated_size > 0 && *stl_relgated < stl_integrated) { 301 | ++stl_relgated; 302 | --stl_relgated_size; 303 | } 304 | 305 | if (stl_relgated_size) { 306 | h_en = stl_relgated[(size_t)((stl_relgated_size - 1) * 0.95 + 0.5)]; 307 | l_en = stl_relgated[(size_t)((stl_relgated_size - 1) * 0.1 + 0.5)]; 308 | free(stl_vector); 309 | return ebur128_energy_to_loudness(h_en) - ebur128_energy_to_loudness(l_en); 310 | } else { 311 | free(stl_vector); 312 | return 0.0; 313 | } 314 | } 315 | 316 | void history_destroy_c(history *hist) { 317 | struct ebur128_dq_entry* entry; 318 | free(hist->block_energy_histogram); 319 | while (!STAILQ_EMPTY(&hist->block_list)) { 320 | entry = STAILQ_FIRST(&hist->block_list); 321 | STAILQ_REMOVE_HEAD(&hist->block_list, entries); 322 | free(entry); 323 | } 324 | free(hist); 325 | } 326 | -------------------------------------------------------------------------------- /src/interp.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Jan Kokemüller 2 | // Copyright (c) 2020 Sebastian Dröge 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | use crate::utils::FrameAccumulator; 23 | use std::f64::consts::PI; 24 | 25 | const ALMOST_ZERO: f64 = 0.000001; 26 | const TAPS: usize = 48; 27 | 28 | /// A circular buffer offering fixed-length continous views into data 29 | /// This is enabled by writing data twice, also to a "shadow"-buffer following the primary buffer, 30 | /// The tradeoff is writing all data twice, the gain is giving the compiler continuous view with 31 | /// predictable length into the data, unlocking some more optimizations 32 | #[derive(Clone, Debug)] 33 | struct RollingBuffer { 34 | buf: [T; TAPS], 35 | position: usize, 36 | } 37 | 38 | impl RollingBuffer { 39 | fn new() -> Self { 40 | assert!(N * 2 <= TAPS); 41 | 42 | let buf: [T; TAPS] = [Default::default(); TAPS]; 43 | 44 | Self { buf, position: N } 45 | } 46 | 47 | #[inline(always)] 48 | fn push_front(&mut self, v: T) { 49 | if self.position == 0 { 50 | self.position = N - 1; 51 | } else { 52 | self.position -= 1; 53 | } 54 | // this is safe, since self.position is always kept below N, which is checked at creation 55 | // to be `<= buf.size() / 2` 56 | unsafe { 57 | *self.buf.get_unchecked_mut(self.position) = v; 58 | *self.buf.get_unchecked_mut(self.position + N) = v; 59 | } 60 | } 61 | } 62 | 63 | impl AsRef<[T; N]> for RollingBuffer { 64 | #[inline(always)] 65 | fn as_ref(&self) -> &[T; N] { 66 | // this is safe, since self.position is always kept below N, which is checked at creation 67 | // to be `<= buf.size() / 2` 68 | unsafe { &*(self.buf.get_unchecked(self.position) as *const T as *const [T; N]) } 69 | } 70 | } 71 | 72 | #[derive(Debug, Clone)] 73 | pub struct InterpF { 74 | filter: [[f32; FACTOR]; ACTIVE_TAPS], 75 | buffer: RollingBuffer, 76 | } 77 | 78 | impl Default for InterpF 79 | where 80 | F: FrameAccumulator + Default, 81 | { 82 | fn default() -> Self { 83 | Self::new() 84 | } 85 | } 86 | 87 | impl InterpF 88 | where 89 | F: FrameAccumulator + Default, 90 | { 91 | pub fn new() -> Self { 92 | assert_eq!(ACTIVE_TAPS * FACTOR, TAPS); 93 | 94 | let mut filter: [[_; FACTOR]; ACTIVE_TAPS] = [[0f32; FACTOR]; ACTIVE_TAPS]; 95 | for (j, coeff) in filter.iter_mut().flat_map(|x| x.iter_mut()).enumerate() { 96 | let j = j as f64; 97 | // Calculate Hanning window, 98 | let window = TAPS + 1; 99 | // Ignore one tap. (Last tap is zero anyways, and we want to hit an even multiple of 48) 100 | let window = (window - 1) as f64; 101 | let w = 0.5 * (1.0 - f64::cos(2.0 * PI * j / window)); 102 | 103 | // Calculate sinc and apply hanning window 104 | let m = j - window / 2.0; 105 | *coeff = if m.abs() > ALMOST_ZERO { 106 | w * f64::sin(m * PI / FACTOR as f64) / (m * PI / FACTOR as f64) 107 | } else { 108 | w 109 | } as f32; 110 | } 111 | 112 | Self { 113 | filter, 114 | buffer: RollingBuffer::new(), 115 | } 116 | } 117 | 118 | pub fn interpolate(&mut self, frame: F) -> [F; FACTOR] { 119 | // Write in Frames in reverse, to enable forward-scanning with filter 120 | self.buffer.push_front(frame); 121 | 122 | let mut output: [F; FACTOR] = [Default::default(); FACTOR]; 123 | 124 | let buf = self.buffer.as_ref(); 125 | 126 | for (filter_coeffs, input_frame) in Iterator::zip(self.filter.iter(), buf) { 127 | for (output_frame, coeff) in Iterator::zip(output.iter_mut(), filter_coeffs) { 128 | output_frame.scale_add(input_frame, *coeff); 129 | } 130 | } 131 | 132 | output 133 | } 134 | 135 | pub fn reset(&mut self) { 136 | self.buffer = RollingBuffer::new(); 137 | } 138 | } 139 | 140 | #[cfg(feature = "c-tests")] 141 | use std::os::raw::c_void; 142 | 143 | #[cfg(feature = "c-tests")] 144 | extern "C" { 145 | pub fn interp_create_c(taps: u32, factor: u32, channels: u32) -> *mut c_void; 146 | pub fn interp_process_c( 147 | interp: *mut c_void, 148 | frames: usize, 149 | src: *const f32, 150 | dst: *mut f32, 151 | ) -> usize; 152 | pub fn interp_destroy_c(interp: *mut c_void); 153 | } 154 | 155 | #[cfg(feature = "c-tests")] 156 | #[cfg(test)] 157 | mod c_tests { 158 | use super::*; 159 | use crate::tests::Signal; 160 | use float_eq::assert_float_eq; 161 | use quickcheck_macros::quickcheck; 162 | 163 | fn process_rust(data_in: &[f32], data_out: &mut [f32], factor: usize, channels: usize) { 164 | macro_rules! process_specialized { 165 | ( $factor:expr, $channels:expr ) => {{ 166 | let mut interp = InterpF::<{ TAPS / $factor }, $factor, [f32; $channels]>::new(); 167 | let (_, data_in, _) = unsafe { data_in.align_to::<[f32; $channels]>() }; 168 | let (_, data_out, _) = unsafe { data_out.align_to_mut::<[f32; $channels]>() }; 169 | for (input_frame, output_frames) in 170 | Iterator::zip(data_in.into_iter(), data_out.chunks_exact_mut(factor)) 171 | { 172 | output_frames.copy_from_slice(&interp.interpolate(*input_frame)); 173 | } 174 | }}; 175 | } 176 | 177 | macro_rules! process_generic { 178 | ( $factor:expr, $channels:expr ) => {{ 179 | let mut interp = 180 | vec![InterpF::<{ TAPS / $factor }, $factor, [f32; 1]>::new(); $channels]; 181 | let frames = data_in.len() / channels; 182 | for frame in 0..frames { 183 | for channel in 0..$channels { 184 | let in_sample = data_in[(frame * $channels) + channel]; 185 | for (o, [output_sample]) in 186 | interp[channel].interpolate([in_sample]).iter().enumerate() 187 | { 188 | let output_frame = frame * factor + o; 189 | data_out[(output_frame * $channels) + channel] = *output_sample; 190 | } 191 | } 192 | } 193 | }}; 194 | } 195 | 196 | match (factor, channels) { 197 | (2, 1) => process_specialized!(2, 1), 198 | (2, 2) => process_specialized!(2, 2), 199 | (2, 4) => process_specialized!(2, 4), 200 | (2, 6) => process_specialized!(2, 6), 201 | (2, 8) => process_specialized!(2, 8), 202 | (4, 1) => process_specialized!(4, 1), 203 | (4, 2) => process_specialized!(4, 2), 204 | (4, 4) => process_specialized!(4, 4), 205 | (4, 6) => process_specialized!(4, 6), 206 | (4, 8) => process_specialized!(4, 8), 207 | (2, c) => process_generic!(2, c), 208 | (4, c) => process_generic!(4, c), 209 | _ => unimplemented!(), 210 | } 211 | } 212 | 213 | #[quickcheck] 214 | fn compare_c_impl(signal: Signal) -> quickcheck::TestResult { 215 | let frames = signal.data.len() / signal.channels as usize; 216 | let factor = if signal.rate < 96_000 { 217 | 4 218 | } else if signal.rate < 192_000 { 219 | 2 220 | } else { 221 | return quickcheck::TestResult::discard(); 222 | }; 223 | 224 | let mut data_out = vec![0.0f32; signal.data.len() * factor]; 225 | let mut data_out_c = vec![0.0f32; signal.data.len() * factor]; 226 | 227 | process_rust( 228 | &signal.data, 229 | &mut data_out, 230 | factor, 231 | signal.channels as usize, 232 | ); 233 | 234 | unsafe { 235 | let interp = interp_create_c(49, factor as u32, signal.channels); 236 | interp_process_c( 237 | interp, 238 | frames, 239 | signal.data.as_ptr(), 240 | data_out_c.as_mut_ptr(), 241 | ); 242 | interp_destroy_c(interp); 243 | } 244 | 245 | for (i, (r, c)) in data_out.iter().zip(data_out_c.iter()).enumerate() { 246 | assert_float_eq!( 247 | *r, 248 | *c, 249 | // For a performance-boost, filter is defined as f32, causing slightly lower precision 250 | abs <= 0.000004, 251 | "Rust and C implementation differ at sample {}", 252 | i 253 | ); 254 | } 255 | 256 | quickcheck::TestResult::passed() 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/capi.rs: -------------------------------------------------------------------------------- 1 | use crate::ebur128; 2 | 3 | use std::{mem, ptr}; 4 | 5 | // ABI compatible with ebur128_state 6 | #[repr(C)] 7 | pub struct State { 8 | mode: i32, 9 | channels: u32, 10 | samplerate: std::os::raw::c_ulong, 11 | internal: *mut ebur128::EbuR128, 12 | } 13 | 14 | #[no_mangle] 15 | pub unsafe extern "C" fn ebur128_get_version(major: *mut i32, minor: *mut i32, patch: *mut i32) { 16 | // We're based on 1.2.6 so let's return that for now 17 | *major = 1; 18 | *minor = 2; 19 | *patch = 6; 20 | } 21 | 22 | #[no_mangle] 23 | pub unsafe extern "C" fn ebur128_init( 24 | channels: u32, 25 | samplerate: std::os::raw::c_ulong, 26 | // Same values as our Mode enum 27 | mode: i32, 28 | ) -> *mut State { 29 | let e = match ebur128::EbuR128::new( 30 | channels, 31 | samplerate as u32, 32 | ebur128::Mode::from_bits_truncate(mode as u8), 33 | ) { 34 | Err(_) => return ptr::null_mut(), 35 | Ok(e) => e, 36 | }; 37 | 38 | let s = State { 39 | mode, 40 | channels, 41 | samplerate, 42 | internal: Box::into_raw(Box::new(e)), 43 | }; 44 | 45 | Box::into_raw(Box::new(s)) 46 | } 47 | 48 | #[no_mangle] 49 | pub unsafe extern "C" fn ebur128_destroy(state: *mut *mut State) { 50 | if state.is_null() || (*state).is_null() { 51 | return; 52 | } 53 | 54 | let s = Box::from_raw(*state); 55 | let e = Box::from_raw(s.internal); 56 | drop(e); 57 | drop(s); 58 | 59 | *state = ptr::null_mut(); 60 | } 61 | 62 | impl From for i32 { 63 | fn from(v: ebur128::Error) -> i32 { 64 | match v { 65 | ebur128::Error::NoMem => 1, 66 | ebur128::Error::InvalidMode => 2, 67 | ebur128::Error::InvalidChannelIndex => 3, 68 | } 69 | } 70 | } 71 | 72 | // Same channel representation 73 | #[no_mangle] 74 | pub unsafe extern "C" fn ebur128_set_channel( 75 | state: *mut State, 76 | channel_number: u32, 77 | value: i32, 78 | ) -> i32 { 79 | let s = &mut *state; 80 | let e = &mut *s.internal; 81 | 82 | match e.set_channel( 83 | channel_number, 84 | mem::transmute::(value), 85 | ) { 86 | Err(err) => err.into(), 87 | Ok(_) => 0, 88 | } 89 | } 90 | 91 | #[no_mangle] 92 | pub unsafe extern "C" fn ebur128_change_parameters( 93 | state: *mut State, 94 | channels: u32, 95 | samplerate: std::os::raw::c_ulong, 96 | ) -> i32 { 97 | let s = &mut *state; 98 | 99 | if s.channels == channels && s.samplerate == samplerate { 100 | return 4; // EBUR128_ERROR_NO_CHANGE 101 | } 102 | 103 | let e = &mut *s.internal; 104 | 105 | match e.change_parameters(channels, samplerate as u32) { 106 | Err(err) => err.into(), 107 | Ok(_) => { 108 | s.channels = channels; 109 | s.samplerate = samplerate; 110 | 0 111 | } 112 | } 113 | } 114 | 115 | #[no_mangle] 116 | pub unsafe extern "C" fn ebur128_set_max_window( 117 | state: *mut State, 118 | window: std::os::raw::c_ulong, 119 | ) -> i32 { 120 | if window > u32::MAX as std::os::raw::c_ulong { 121 | return ebur128::Error::NoMem.into(); 122 | } 123 | 124 | let s = &mut *state; 125 | let e = &mut *s.internal; 126 | 127 | if e.max_window() == window as usize { 128 | return 4; // EBUR128_ERROR_NO_CHANGE 129 | } 130 | 131 | match e.set_max_window(window as u32) { 132 | Err(err) => err.into(), 133 | Ok(_) => 0, 134 | } 135 | } 136 | 137 | #[no_mangle] 138 | pub unsafe extern "C" fn ebur128_set_max_history( 139 | state: *mut State, 140 | history: std::os::raw::c_ulong, 141 | ) -> i32 { 142 | if history > u32::MAX as std::os::raw::c_ulong { 143 | return ebur128::Error::NoMem.into(); 144 | } 145 | 146 | let s = &mut *state; 147 | let e = &mut *s.internal; 148 | 149 | if e.max_history() == history as usize { 150 | return 4; // EBUR128_ERROR_NO_CHANGE 151 | } 152 | 153 | match e.set_max_history(history as u32) { 154 | Err(err) => err.into(), 155 | Ok(_) => 0, 156 | } 157 | } 158 | 159 | #[no_mangle] 160 | pub unsafe extern "C" fn ebur128_add_frames_short( 161 | state: *mut State, 162 | src: *const i16, 163 | frames: usize, 164 | ) -> i32 { 165 | use std::slice; 166 | 167 | let s = &mut *state; 168 | let e = &mut *s.internal; 169 | 170 | let samples = match frames.checked_mul(s.channels as usize) { 171 | None => return crate::ebur128::Error::NoMem.into(), 172 | Some(samples) => samples, 173 | }; 174 | 175 | match e.add_frames_i16(slice::from_raw_parts(src, samples)) { 176 | Err(err) => err.into(), 177 | Ok(_) => 0, 178 | } 179 | } 180 | 181 | #[no_mangle] 182 | pub unsafe extern "C" fn ebur128_add_frames_int( 183 | state: *mut State, 184 | src: *const i32, 185 | frames: usize, 186 | ) -> i32 { 187 | use std::slice; 188 | 189 | let s = &mut *state; 190 | let e = &mut *s.internal; 191 | 192 | let samples = match frames.checked_mul(s.channels as usize) { 193 | None => return crate::ebur128::Error::NoMem.into(), 194 | Some(samples) => samples, 195 | }; 196 | 197 | match e.add_frames_i32(slice::from_raw_parts(src, samples)) { 198 | Err(err) => err.into(), 199 | Ok(_) => 0, 200 | } 201 | } 202 | 203 | #[no_mangle] 204 | pub unsafe extern "C" fn ebur128_add_frames_float( 205 | state: *mut State, 206 | src: *const f32, 207 | frames: usize, 208 | ) -> i32 { 209 | use std::slice; 210 | 211 | let s = &mut *state; 212 | let e = &mut *s.internal; 213 | 214 | let samples = match frames.checked_mul(s.channels as usize) { 215 | None => return crate::ebur128::Error::NoMem.into(), 216 | Some(samples) => samples, 217 | }; 218 | 219 | match e.add_frames_f32(slice::from_raw_parts(src, samples)) { 220 | Err(err) => err.into(), 221 | Ok(_) => 0, 222 | } 223 | } 224 | 225 | #[no_mangle] 226 | pub unsafe extern "C" fn ebur128_add_frames_double( 227 | state: *mut State, 228 | src: *const f64, 229 | frames: usize, 230 | ) -> i32 { 231 | use std::slice; 232 | 233 | let s = &mut *state; 234 | let e = &mut *s.internal; 235 | 236 | let samples = match frames.checked_mul(s.channels as usize) { 237 | None => return crate::ebur128::Error::NoMem.into(), 238 | Some(samples) => samples, 239 | }; 240 | 241 | match e.add_frames_f64(slice::from_raw_parts(src, samples)) { 242 | Err(err) => err.into(), 243 | Ok(_) => 0, 244 | } 245 | } 246 | 247 | #[no_mangle] 248 | pub unsafe extern "C" fn ebur128_loudness_global(state: *mut State, out: *mut f64) -> i32 { 249 | let s = &*state; 250 | let e = &*s.internal; 251 | 252 | match e.loudness_global() { 253 | Err(err) => err.into(), 254 | Ok(val) => { 255 | *out = val; 256 | 0 257 | } 258 | } 259 | } 260 | 261 | #[no_mangle] 262 | pub unsafe extern "C" fn ebur128_loudness_global_multiple( 263 | state: *mut *mut State, 264 | size: usize, 265 | out: *mut f64, 266 | ) -> i32 { 267 | use std::slice; 268 | 269 | let s = slice::from_raw_parts(state, size); 270 | let iter = s.iter().copied().map(|s: *mut State| &*(*s).internal); 271 | 272 | match ebur128::EbuR128::loudness_global_multiple(iter) { 273 | Err(err) => err.into(), 274 | Ok(val) => { 275 | *out = val; 276 | 0 277 | } 278 | } 279 | } 280 | 281 | #[no_mangle] 282 | pub unsafe extern "C" fn ebur128_loudness_momentary(state: *mut State, out: *mut f64) -> i32 { 283 | let s = &*state; 284 | let e = &*s.internal; 285 | 286 | match e.loudness_momentary() { 287 | Err(err) => err.into(), 288 | Ok(val) => { 289 | *out = val; 290 | 0 291 | } 292 | } 293 | } 294 | 295 | #[no_mangle] 296 | pub unsafe extern "C" fn ebur128_loudness_shortterm(state: *mut State, out: *mut f64) -> i32 { 297 | let s = &*state; 298 | let e = &*s.internal; 299 | 300 | match e.loudness_shortterm() { 301 | Err(err) => err.into(), 302 | Ok(val) => { 303 | *out = val; 304 | 0 305 | } 306 | } 307 | } 308 | 309 | #[no_mangle] 310 | pub unsafe extern "C" fn ebur128_loudness_window( 311 | state: *mut State, 312 | window: std::os::raw::c_ulong, 313 | out: *mut f64, 314 | ) -> i32 { 315 | let s = &*state; 316 | let e = &*s.internal; 317 | 318 | if window > u32::MAX as std::os::raw::c_ulong { 319 | return ebur128::Error::NoMem.into(); 320 | } 321 | 322 | match e.loudness_window(window as u32) { 323 | Err(err) => err.into(), 324 | Ok(val) => { 325 | *out = val; 326 | 0 327 | } 328 | } 329 | } 330 | 331 | #[no_mangle] 332 | pub unsafe extern "C" fn ebur128_loudness_range(state: *mut State, out: *mut f64) -> i32 { 333 | let s = &*state; 334 | let e = &*s.internal; 335 | 336 | match e.loudness_range() { 337 | Err(err) => err.into(), 338 | Ok(val) => { 339 | *out = val; 340 | 0 341 | } 342 | } 343 | } 344 | 345 | #[no_mangle] 346 | pub unsafe extern "C" fn ebur128_loudness_range_multiple( 347 | state: *mut *mut State, 348 | size: usize, 349 | out: *mut f64, 350 | ) -> i32 { 351 | use std::slice; 352 | 353 | let s = slice::from_raw_parts(state, size); 354 | let iter = s.iter().copied().map(|s: *mut State| &*(*s).internal); 355 | 356 | match ebur128::EbuR128::loudness_range_multiple(iter) { 357 | Err(err) => err.into(), 358 | Ok(val) => { 359 | *out = val; 360 | 0 361 | } 362 | } 363 | } 364 | 365 | #[no_mangle] 366 | pub unsafe extern "C" fn ebur128_sample_peak( 367 | state: *mut State, 368 | channel_number: u32, 369 | out: *mut f64, 370 | ) -> i32 { 371 | let s = &*state; 372 | let e = &*s.internal; 373 | 374 | match e.sample_peak(channel_number) { 375 | Err(err) => err.into(), 376 | Ok(val) => { 377 | *out = val; 378 | 0 379 | } 380 | } 381 | } 382 | 383 | #[no_mangle] 384 | pub unsafe extern "C" fn ebur128_prev_sample_peak( 385 | state: *mut State, 386 | channel_number: u32, 387 | out: *mut f64, 388 | ) -> i32 { 389 | let s = &*state; 390 | let e = &*s.internal; 391 | 392 | match e.prev_sample_peak(channel_number) { 393 | Err(err) => err.into(), 394 | Ok(val) => { 395 | *out = val; 396 | 0 397 | } 398 | } 399 | } 400 | 401 | #[no_mangle] 402 | pub unsafe extern "C" fn ebur128_true_peak( 403 | state: *mut State, 404 | channel_number: u32, 405 | out: *mut f64, 406 | ) -> i32 { 407 | let s = &*state; 408 | let e = &*s.internal; 409 | 410 | match e.true_peak(channel_number) { 411 | Err(err) => err.into(), 412 | Ok(val) => { 413 | *out = val; 414 | 0 415 | } 416 | } 417 | } 418 | 419 | #[no_mangle] 420 | pub unsafe extern "C" fn ebur128_prev_true_peak( 421 | state: *mut State, 422 | channel_number: u32, 423 | out: *mut f64, 424 | ) -> i32 { 425 | let s = &*state; 426 | let e = &*s.internal; 427 | 428 | match e.prev_true_peak(channel_number) { 429 | Err(err) => err.into(), 430 | Ok(val) => { 431 | *out = val; 432 | 0 433 | } 434 | } 435 | } 436 | 437 | #[no_mangle] 438 | pub unsafe extern "C" fn ebur128_relative_threshold(state: *mut State, out: *mut f64) -> i32 { 439 | let s = &*state; 440 | let e = &*s.internal; 441 | 442 | match e.relative_threshold() { 443 | Err(err) => err.into(), 444 | Ok(val) => { 445 | *out = val; 446 | 0 447 | } 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /benches/filter.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use ebur128::filter; 4 | 5 | // Run filter benchmarks on the same filter instance to not measure the setup time 6 | // and measure once with and another time without calculating the sample peak. 7 | // 8 | // We don't calculate the true peak because that has its own benchmark. 9 | 10 | pub fn criterion_benchmark(c: &mut Criterion) { 11 | let mut group = c.benchmark_group("filter create: 48kHz 2ch"); 12 | 13 | #[cfg(feature = "c-tests")] 14 | group.bench_function("C", |b| { 15 | b.iter(|| unsafe { 16 | let f = filter::filter_create_c( 17 | black_box(48_000), 18 | black_box(2), 19 | black_box(0), 20 | black_box(0), 21 | ); 22 | filter::filter_destroy_c(f); 23 | }) 24 | }); 25 | 26 | group.bench_function("Rust", |b| { 27 | b.iter(|| { 28 | let f = filter::Filter::new( 29 | black_box(48_000), 30 | black_box(2), 31 | black_box(false), 32 | black_box(false), 33 | ); 34 | drop(black_box(f)); 35 | }) 36 | }); 37 | 38 | group.finish(); 39 | 40 | for (sample_peak, name) in &[(true, " with sample peak"), (false, "")] { 41 | #[cfg(feature = "c-tests")] 42 | let channel_map_c = [1; 2]; 43 | let channel_map = [ebur128::Channel::Left; 2]; 44 | let mut data_out = vec![0.0f64; 19200 * 2]; 45 | let mut data = vec![0i16; 19200 * 2]; 46 | let mut data_planar = vec![0i16; 19200 * 2]; 47 | let (fst, snd) = data_planar.split_at_mut(19200); 48 | let mut accumulator = 0.0; 49 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 50 | for (out, (fst, snd)) in Iterator::zip( 51 | data.chunks_exact_mut(2), 52 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 53 | ) { 54 | let val = f32::sin(accumulator) * i16::MAX as f32; 55 | out[0] = val as i16; 56 | out[1] = val as i16; 57 | *fst = val as i16; 58 | *snd = val as i16; 59 | accumulator += step; 60 | } 61 | 62 | let mut group = c.benchmark_group(format!("filter process: 48kHz 2ch i16{name}")); 63 | 64 | #[cfg(feature = "c-tests")] 65 | unsafe { 66 | let f = filter::filter_create_c(48_000, 2, i32::from(*sample_peak), 0); 67 | group.bench_function("C", |b| { 68 | b.iter(|| { 69 | filter::filter_process_short_c( 70 | black_box(f), 71 | black_box(data.len() / 2), 72 | black_box(data.as_ptr()), 73 | black_box(data_out.as_mut_ptr()), 74 | black_box(channel_map_c.as_ptr()), 75 | ) 76 | }) 77 | }); 78 | filter::filter_destroy_c(f); 79 | } 80 | 81 | { 82 | let mut f = filter::Filter::new(48_000, 2, *sample_peak, false); 83 | group.bench_function("Rust/Interleaved", |b| { 84 | b.iter(|| { 85 | f.process( 86 | black_box(ebur128::Interleaved::new(&data, 2).unwrap()), 87 | black_box(&mut data_out), 88 | black_box(0), 89 | black_box(&channel_map), 90 | ); 91 | }) 92 | }); 93 | 94 | let mut f = filter::Filter::new(48_000, 2, *sample_peak, false); 95 | group.bench_function("Rust/Planar", |b| { 96 | b.iter(|| { 97 | f.process( 98 | black_box(ebur128::Planar::new(&[fst, snd]).unwrap()), 99 | black_box(&mut data_out), 100 | black_box(0), 101 | black_box(&channel_map), 102 | ); 103 | }) 104 | }); 105 | } 106 | 107 | group.finish(); 108 | 109 | let mut data = vec![0i32; 19200 * 2]; 110 | let mut data_planar = vec![0i32; 19200 * 2]; 111 | let (fst, snd) = data_planar.split_at_mut(19200); 112 | let mut accumulator = 0.0; 113 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 114 | for (out, (fst, snd)) in Iterator::zip( 115 | data.chunks_exact_mut(2), 116 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 117 | ) { 118 | let val = f32::sin(accumulator) * i32::MAX as f32; 119 | out[0] = val as i32; 120 | out[1] = val as i32; 121 | *fst = val as i32; 122 | *snd = val as i32; 123 | accumulator += step; 124 | } 125 | 126 | let mut group = c.benchmark_group(format!("filter process: 48kHz 2ch i32{name}")); 127 | 128 | #[cfg(feature = "c-tests")] 129 | unsafe { 130 | let f = filter::filter_create_c(48_000, 2, i32::from(*sample_peak), 0); 131 | group.bench_function("C", |b| { 132 | b.iter(|| { 133 | filter::filter_process_int_c( 134 | black_box(f), 135 | black_box(data.len() / 2), 136 | black_box(data.as_ptr()), 137 | black_box(data_out.as_mut_ptr()), 138 | black_box(channel_map_c.as_ptr()), 139 | ) 140 | }) 141 | }); 142 | filter::filter_destroy_c(f); 143 | } 144 | 145 | { 146 | let mut f = filter::Filter::new(48_000, 2, *sample_peak, false); 147 | group.bench_function("Rust/Interleaved", |b| { 148 | b.iter(|| { 149 | f.process( 150 | black_box(ebur128::Interleaved::new(&data, 2).unwrap()), 151 | black_box(&mut data_out), 152 | black_box(0), 153 | black_box(&channel_map), 154 | ); 155 | }) 156 | }); 157 | 158 | let mut f = filter::Filter::new(48_000, 2, *sample_peak, false); 159 | group.bench_function("Rust/Planar", |b| { 160 | b.iter(|| { 161 | f.process( 162 | black_box(ebur128::Planar::new(&[fst, snd]).unwrap()), 163 | black_box(&mut data_out), 164 | black_box(0), 165 | black_box(&channel_map), 166 | ); 167 | }) 168 | }); 169 | } 170 | 171 | group.finish(); 172 | 173 | let mut data = vec![0.0f32; 19200 * 2]; 174 | let mut data_planar = vec![0.0f32; 19200 * 2]; 175 | let (fst, snd) = data_planar.split_at_mut(19200); 176 | let mut accumulator = 0.0; 177 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 178 | for (out, (fst, snd)) in Iterator::zip( 179 | data.chunks_exact_mut(2), 180 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 181 | ) { 182 | let val = f32::sin(accumulator); 183 | out[0] = val; 184 | out[1] = val; 185 | *fst = val; 186 | *snd = val; 187 | accumulator += step; 188 | } 189 | 190 | let mut group = c.benchmark_group(format!("filter process: 48kHz 2ch f32{name}")); 191 | 192 | #[cfg(feature = "c-tests")] 193 | unsafe { 194 | let f = filter::filter_create_c(48_000, 2, i32::from(*sample_peak), 0); 195 | group.bench_function("C", |b| { 196 | b.iter(|| { 197 | filter::filter_process_float_c( 198 | black_box(f), 199 | black_box(data.len() / 2), 200 | black_box(data.as_ptr()), 201 | black_box(data_out.as_mut_ptr()), 202 | black_box(channel_map_c.as_ptr()), 203 | ) 204 | }) 205 | }); 206 | filter::filter_destroy_c(f); 207 | } 208 | 209 | { 210 | let mut f = filter::Filter::new(48_000, 2, *sample_peak, false); 211 | group.bench_function("Rust/Interleaved", |b| { 212 | b.iter(|| { 213 | f.process( 214 | black_box(ebur128::Interleaved::new(&data, 2).unwrap()), 215 | black_box(&mut data_out), 216 | black_box(0), 217 | black_box(&channel_map), 218 | ); 219 | }) 220 | }); 221 | 222 | let mut f = filter::Filter::new(48_000, 2, *sample_peak, false); 223 | group.bench_function("Rust/Planar", |b| { 224 | b.iter(|| { 225 | f.process( 226 | black_box(ebur128::Planar::new(&[fst, snd]).unwrap()), 227 | black_box(&mut data_out), 228 | black_box(0), 229 | black_box(&channel_map), 230 | ); 231 | }) 232 | }); 233 | } 234 | 235 | group.finish(); 236 | 237 | let mut data = vec![0.0f64; 19200 * 2]; 238 | let mut data_planar = vec![0.0f64; 19200 * 2]; 239 | let (fst, snd) = data_planar.split_at_mut(19200); 240 | let mut accumulator = 0.0; 241 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 242 | for (out, (fst, snd)) in Iterator::zip( 243 | data.chunks_exact_mut(2), 244 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 245 | ) { 246 | let val = f32::sin(accumulator); 247 | out[0] = val as f64; 248 | out[1] = val as f64; 249 | *fst = val as f64; 250 | *snd = val as f64; 251 | accumulator += step; 252 | } 253 | 254 | let mut group = c.benchmark_group(format!("filter process: 48kHz 2ch f64{name}")); 255 | 256 | #[cfg(feature = "c-tests")] 257 | unsafe { 258 | let f = filter::filter_create_c(48_000, 2, i32::from(*sample_peak), 0); 259 | group.bench_function("C", |b| { 260 | b.iter(|| { 261 | filter::filter_process_double_c( 262 | black_box(f), 263 | black_box(data.len() / 2), 264 | black_box(data.as_ptr()), 265 | black_box(data_out.as_mut_ptr()), 266 | black_box(channel_map_c.as_ptr()), 267 | ) 268 | }) 269 | }); 270 | filter::filter_destroy_c(f); 271 | } 272 | 273 | { 274 | let mut f = filter::Filter::new(48_000, 2, *sample_peak, false); 275 | group.bench_function("Rust/Interleaved", |b| { 276 | b.iter(|| { 277 | f.process( 278 | black_box(ebur128::Interleaved::new(&data, 2).unwrap()), 279 | black_box(&mut data_out), 280 | black_box(0), 281 | black_box(&channel_map), 282 | ); 283 | }) 284 | }); 285 | 286 | let mut f = filter::Filter::new(48_000, 2, *sample_peak, false); 287 | group.bench_function("Rust/Planar", |b| { 288 | b.iter(|| { 289 | f.process( 290 | black_box(ebur128::Planar::new(&[fst, snd]).unwrap()), 291 | black_box(&mut data_out), 292 | black_box(0), 293 | black_box(&channel_map), 294 | ); 295 | }) 296 | }); 297 | } 298 | 299 | group.finish(); 300 | } 301 | } 302 | 303 | criterion_group!(benches, criterion_benchmark); 304 | criterion_main!(benches); 305 | -------------------------------------------------------------------------------- /benches/ebur128.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use ebur128::{EbuR128, Mode}; 4 | use ebur128_c::Mode as ModeC; 5 | 6 | #[cfg(feature = "c-tests")] 7 | use ebur128_c::EbuR128 as EbuR128C; 8 | 9 | #[cfg(feature = "c-tests")] 10 | fn get_results_c(ebu: &EbuR128C, mode: ModeC) { 11 | if mode.contains(ModeC::I) { 12 | black_box(ebu.loudness_global().unwrap()); 13 | } 14 | if mode.contains(ModeC::M) { 15 | black_box(ebu.loudness_momentary().unwrap()); 16 | } 17 | if mode.contains(ModeC::S) { 18 | black_box(ebu.loudness_shortterm().unwrap()); 19 | } 20 | black_box(ebu.loudness_window(1).unwrap()); 21 | 22 | if mode.contains(ModeC::LRA) { 23 | black_box(ebu.loudness_range().unwrap()); 24 | } 25 | 26 | if mode.contains(ModeC::SAMPLE_PEAK) { 27 | black_box(ebu.sample_peak(0).unwrap()); 28 | black_box(ebu.sample_peak(1).unwrap()); 29 | black_box(ebu.prev_sample_peak(0).unwrap()); 30 | black_box(ebu.prev_sample_peak(1).unwrap()); 31 | } 32 | 33 | if mode.contains(ModeC::TRUE_PEAK) { 34 | black_box(ebu.true_peak(0).unwrap()); 35 | black_box(ebu.true_peak(1).unwrap()); 36 | black_box(ebu.prev_true_peak(0).unwrap()); 37 | black_box(ebu.prev_true_peak(1).unwrap()); 38 | } 39 | 40 | if mode.contains(ModeC::I) { 41 | black_box(ebu.relative_threshold().unwrap()); 42 | } 43 | } 44 | 45 | fn get_results(ebu: &EbuR128, mode: Mode) { 46 | if mode.contains(Mode::I) { 47 | black_box(ebu.loudness_global().unwrap()); 48 | } 49 | if mode.contains(Mode::M) { 50 | black_box(ebu.loudness_momentary().unwrap()); 51 | } 52 | if mode.contains(Mode::S) { 53 | black_box(ebu.loudness_shortterm().unwrap()); 54 | } 55 | black_box(ebu.loudness_window(1).unwrap()); 56 | 57 | if mode.contains(Mode::LRA) { 58 | black_box(ebu.loudness_range().unwrap()); 59 | } 60 | 61 | if mode.contains(Mode::SAMPLE_PEAK) { 62 | black_box(ebu.sample_peak(0).unwrap()); 63 | black_box(ebu.sample_peak(1).unwrap()); 64 | black_box(ebu.prev_sample_peak(0).unwrap()); 65 | black_box(ebu.prev_sample_peak(1).unwrap()); 66 | } 67 | 68 | if mode.contains(Mode::TRUE_PEAK) { 69 | black_box(ebu.true_peak(0).unwrap()); 70 | black_box(ebu.true_peak(1).unwrap()); 71 | black_box(ebu.prev_true_peak(0).unwrap()); 72 | black_box(ebu.prev_true_peak(1).unwrap()); 73 | } 74 | 75 | if mode.contains(Mode::I) { 76 | black_box(ebu.relative_threshold().unwrap()); 77 | } 78 | } 79 | 80 | pub fn criterion_benchmark(c: &mut Criterion) { 81 | let modes = [ 82 | ("M", Mode::M, ModeC::M), 83 | ("S", Mode::S, ModeC::S), 84 | ("I", Mode::I, ModeC::I), 85 | ("LRA", Mode::LRA, ModeC::LRA), 86 | ("SAMPLE_PEAK", Mode::SAMPLE_PEAK, ModeC::SAMPLE_PEAK), 87 | ("TRUE_PEAK", Mode::TRUE_PEAK, ModeC::TRUE_PEAK), 88 | ( 89 | "I histogram", 90 | Mode::I | Mode::HISTOGRAM, 91 | ModeC::I | ModeC::HISTOGRAM, 92 | ), 93 | ( 94 | "LRA histogram", 95 | Mode::LRA | Mode::HISTOGRAM, 96 | ModeC::LRA | ModeC::HISTOGRAM, 97 | ), 98 | ( 99 | "all", 100 | Mode::all() & !Mode::HISTOGRAM, 101 | ModeC::all() & !ModeC::HISTOGRAM, 102 | ), 103 | ("all histogram", Mode::all(), ModeC::all()), 104 | ]; 105 | 106 | #[allow(unused_variables)] 107 | for (name, mode, mode_c) in &modes { 108 | let mode = *mode; 109 | #[cfg(feature = "c-tests")] 110 | let mode_c = *mode_c; 111 | 112 | let mut group = c.benchmark_group(format!("ebur128 create: 48kHz 2ch {name}")); 113 | 114 | #[cfg(feature = "c-tests")] 115 | { 116 | group.bench_function("C", |b| { 117 | b.iter(|| { 118 | let ebu = 119 | EbuR128C::new(black_box(2), black_box(48_000), black_box(mode_c)).unwrap(); 120 | drop(black_box(ebu)); 121 | }) 122 | }); 123 | } 124 | group.bench_function("Rust", |b| { 125 | b.iter(|| { 126 | let ebu = EbuR128::new(black_box(2), black_box(48_000), black_box(mode)).unwrap(); 127 | drop(black_box(ebu)); 128 | }) 129 | }); 130 | 131 | group.finish(); 132 | 133 | let mut data = vec![0i16; 48_000 * 5 * 2]; 134 | let mut data_planar = vec![0i16; 48_000 * 5 * 2]; 135 | let (fst, snd) = data_planar.split_at_mut(48_000 * 5); 136 | let mut accumulator = 0.0; 137 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 138 | for (out, (fst, snd)) in Iterator::zip( 139 | data.chunks_exact_mut(2), 140 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 141 | ) { 142 | let val = f32::sin(accumulator) * i16::MAX as f32; 143 | out[0] = val as i16; 144 | out[1] = val as i16; 145 | *fst = val as i16; 146 | *snd = val as i16; 147 | accumulator += step; 148 | } 149 | 150 | let mut group = c.benchmark_group(format!("ebur128 process: 48kHz i16 2ch {name}")); 151 | 152 | #[cfg(feature = "c-tests")] 153 | { 154 | group.bench_function("C", |b| { 155 | b.iter(|| { 156 | let mut ebu = 157 | EbuR128C::new(black_box(2), black_box(48_000), black_box(mode_c)).unwrap(); 158 | ebu.add_frames_i16(&data).unwrap(); 159 | 160 | get_results_c(&ebu, black_box(mode_c)); 161 | }) 162 | }); 163 | } 164 | group.bench_function("Rust/Interleaved", |b| { 165 | b.iter(|| { 166 | let mut ebu = 167 | EbuR128::new(black_box(2), black_box(48_000), black_box(mode)).unwrap(); 168 | ebu.add_frames_i16(&data).unwrap(); 169 | 170 | get_results(&ebu, black_box(mode)); 171 | }) 172 | }); 173 | group.bench_function("Rust/Planar", |b| { 174 | b.iter(|| { 175 | let mut ebu = 176 | EbuR128::new(black_box(2), black_box(48_000), black_box(mode)).unwrap(); 177 | ebu.add_frames_planar_i16(&[fst, snd]).unwrap(); 178 | 179 | get_results(&ebu, black_box(mode)); 180 | }) 181 | }); 182 | 183 | group.finish(); 184 | 185 | let mut data = vec![0i32; 48_000 * 5 * 2]; 186 | let mut data_planar = vec![0i32; 48_000 * 5 * 2]; 187 | let (fst, snd) = data_planar.split_at_mut(48_000 * 5); 188 | let mut accumulator = 0.0; 189 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 190 | for (out, (fst, snd)) in Iterator::zip( 191 | data.chunks_exact_mut(2), 192 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 193 | ) { 194 | let val = f32::sin(accumulator) * i32::MAX as f32; 195 | out[0] = val as i32; 196 | out[1] = val as i32; 197 | *fst = val as i32; 198 | *snd = val as i32; 199 | accumulator += step; 200 | } 201 | 202 | let mut group = c.benchmark_group(format!("ebur128 process: 48kHz i32 2ch {name}")); 203 | 204 | #[cfg(feature = "c-tests")] 205 | { 206 | group.bench_function("C", |b| { 207 | b.iter(|| { 208 | let mut ebu = 209 | EbuR128C::new(black_box(2), black_box(48_000), black_box(mode_c)).unwrap(); 210 | ebu.add_frames_i32(&data).unwrap(); 211 | 212 | get_results_c(&ebu, black_box(mode_c)); 213 | }) 214 | }); 215 | } 216 | group.bench_function("Rust/Interleaved", |b| { 217 | b.iter(|| { 218 | let mut ebu = 219 | EbuR128::new(black_box(2), black_box(48_000), black_box(mode)).unwrap(); 220 | ebu.add_frames_i32(&data).unwrap(); 221 | 222 | get_results(&ebu, black_box(mode)); 223 | }) 224 | }); 225 | group.bench_function("Rust/Planar", |b| { 226 | b.iter(|| { 227 | let mut ebu = 228 | EbuR128::new(black_box(2), black_box(48_000), black_box(mode)).unwrap(); 229 | ebu.add_frames_planar_i32(&[fst, snd]).unwrap(); 230 | 231 | get_results(&ebu, black_box(mode)); 232 | }) 233 | }); 234 | 235 | group.finish(); 236 | 237 | let mut data = vec![0.0f32; 48_000 * 5 * 2]; 238 | let mut data_planar = vec![0.0f32; 48_000 * 5 * 2]; 239 | let (fst, snd) = data_planar.split_at_mut(48_000 * 5); 240 | let mut accumulator = 0.0; 241 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 242 | for (out, (fst, snd)) in Iterator::zip( 243 | data.chunks_exact_mut(2), 244 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 245 | ) { 246 | let val = f32::sin(accumulator); 247 | out[0] = val; 248 | out[1] = val; 249 | *fst = val; 250 | *snd = val; 251 | accumulator += step; 252 | } 253 | 254 | let mut group = c.benchmark_group(format!("ebur128 process: 48kHz f32 2ch {name}")); 255 | 256 | #[cfg(feature = "c-tests")] 257 | { 258 | group.bench_function("C", |b| { 259 | b.iter(|| { 260 | let mut ebu = 261 | EbuR128C::new(black_box(2), black_box(48_000), black_box(mode_c)).unwrap(); 262 | ebu.add_frames_f32(&data).unwrap(); 263 | 264 | get_results_c(&ebu, black_box(mode_c)); 265 | }) 266 | }); 267 | } 268 | group.bench_function("Rust/Interleaved", |b| { 269 | b.iter(|| { 270 | let mut ebu = 271 | EbuR128::new(black_box(2), black_box(48_000), black_box(mode)).unwrap(); 272 | ebu.add_frames_f32(&data).unwrap(); 273 | 274 | get_results(&ebu, black_box(mode)); 275 | }) 276 | }); 277 | group.bench_function("Rust/Planar", |b| { 278 | b.iter(|| { 279 | let mut ebu = 280 | EbuR128::new(black_box(2), black_box(48_000), black_box(mode)).unwrap(); 281 | ebu.add_frames_planar_f32(&[fst, snd]).unwrap(); 282 | 283 | get_results(&ebu, black_box(mode)); 284 | }) 285 | }); 286 | 287 | group.finish(); 288 | 289 | let mut data = vec![0.0f64; 48_000 * 5 * 2]; 290 | let mut data_planar = vec![0.0f64; 48_000 * 5 * 2]; 291 | let (fst, snd) = data_planar.split_at_mut(48_000 * 5); 292 | let mut accumulator = 0.0; 293 | let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0; 294 | for (out, (fst, snd)) in Iterator::zip( 295 | data.chunks_exact_mut(2), 296 | Iterator::zip(fst.iter_mut(), snd.iter_mut()), 297 | ) { 298 | let val = f32::sin(accumulator); 299 | out[0] = val as f64; 300 | out[1] = val as f64; 301 | *fst = val as f64; 302 | *snd = val as f64; 303 | accumulator += step; 304 | } 305 | 306 | let mut group = c.benchmark_group(format!("ebur128 process: 48kHz f64 2ch {name}")); 307 | 308 | #[cfg(feature = "c-tests")] 309 | { 310 | group.bench_function("C", |b| { 311 | b.iter(|| { 312 | let mut ebu = 313 | EbuR128C::new(black_box(2), black_box(48_000), black_box(mode_c)).unwrap(); 314 | ebu.add_frames_f64(&data).unwrap(); 315 | 316 | get_results_c(&ebu, black_box(mode_c)); 317 | }) 318 | }); 319 | } 320 | group.bench_function("Rust/Interleaved", |b| { 321 | b.iter(|| { 322 | let mut ebu = 323 | EbuR128::new(black_box(2), black_box(48_000), black_box(mode)).unwrap(); 324 | ebu.add_frames_f64(&data).unwrap(); 325 | 326 | get_results(&ebu, black_box(mode)); 327 | }) 328 | }); 329 | group.bench_function("Rust/Planar", |b| { 330 | b.iter(|| { 331 | let mut ebu = 332 | EbuR128::new(black_box(2), black_box(48_000), black_box(mode)).unwrap(); 333 | ebu.add_frames_planar_f64(&[fst, snd]).unwrap(); 334 | 335 | get_results(&ebu, black_box(mode)); 336 | }) 337 | }); 338 | 339 | group.finish(); 340 | } 341 | } 342 | 343 | criterion_group!(benches, criterion_benchmark); 344 | criterion_main!(benches); 345 | -------------------------------------------------------------------------------- /src/true_peak.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Jan Kokemüller 2 | // Copyright (c) 2020 Sebastian Dröge 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | use crate::interp::InterpF; 23 | use crate::utils::{FrameAccumulator, Sample}; 24 | use dasp_frame::Frame; 25 | use smallvec::{smallvec, SmallVec}; 26 | 27 | use UpsamplingScanner::*; 28 | 29 | #[derive(Debug)] 30 | enum UpsamplingScanner { 31 | Mono2F(InterpF<24, 2, [f32; 1]>), 32 | Stereo2F(InterpF<24, 2, [f32; 2]>), 33 | Quad2F(InterpF<24, 2, [f32; 4]>), 34 | Surround2F(InterpF<24, 2, [f32; 6]>), 35 | OctoSurround2F(InterpF<24, 2, [f32; 8]>), 36 | Mono4F(InterpF<12, 4, [f32; 1]>), 37 | Stereo4F(InterpF<12, 4, [f32; 2]>), 38 | Quad4F(InterpF<12, 4, [f32; 4]>), 39 | Surround4F(InterpF<12, 4, [f32; 6]>), 40 | OctoSurround4F(InterpF<12, 4, [f32; 8]>), 41 | Generic2F(Box<[InterpF<24, 2, [f32; 1]>]>), 42 | Generic4F(Box<[InterpF<12, 4, [f32; 1]>]>), 43 | } 44 | 45 | impl UpsamplingScanner { 46 | fn new(rate: u32, channels: u32) -> Option { 47 | enum Factor { 48 | Four, 49 | Two, 50 | } 51 | let interp_factor = if rate < 96_000 { 52 | Factor::Four 53 | } else if rate < 192_000 { 54 | Factor::Two 55 | } else { 56 | return None; 57 | }; 58 | 59 | Some(match (channels as usize, interp_factor) { 60 | (1, Factor::Two) => Mono2F(InterpF::new()), 61 | (2, Factor::Two) => Stereo2F(InterpF::new()), 62 | (4, Factor::Two) => Quad2F(InterpF::new()), 63 | (6, Factor::Two) => Surround2F(InterpF::new()), 64 | (8, Factor::Two) => OctoSurround2F(InterpF::new()), 65 | (1, Factor::Four) => Mono4F(InterpF::new()), 66 | (2, Factor::Four) => Stereo4F(InterpF::new()), 67 | (4, Factor::Four) => Quad4F(InterpF::new()), 68 | (6, Factor::Four) => Surround4F(InterpF::new()), 69 | (8, Factor::Four) => OctoSurround4F(InterpF::new()), 70 | (c, Factor::Two) => Generic2F(vec![InterpF::new(); c].into()), 71 | (c, Factor::Four) => Generic4F(vec![InterpF::new(); c].into()), 72 | }) 73 | } 74 | 75 | pub fn check_true_peak<'a, T: Sample + 'a, S: crate::Samples<'a, T>>( 76 | &mut self, 77 | src: S, 78 | peaks: &mut [f64], 79 | ) { 80 | macro_rules! tp_specialized_impl { 81 | ( $channels:expr, $interpolator:expr ) => {{ 82 | const CHANNELS: usize = $channels; 83 | assert!(src.channels() == CHANNELS && peaks.len() == CHANNELS); 84 | let mut tmp_peaks = <[f32; CHANNELS]>::from_fn(|i| peaks[i] as f32); 85 | 86 | src.foreach_frame(|frame: [T; CHANNELS]| { 87 | let frame_f32: [f32; CHANNELS] = Frame::map(frame, |s| s.to_sample::()); 88 | for new_frame in &$interpolator.interpolate(frame_f32) { 89 | tmp_peaks.retain_max_samples(&Frame::map(*new_frame, |s| s.abs())); 90 | } 91 | }); 92 | for (dst, src) in Iterator::zip(peaks.into_iter(), &tmp_peaks) { 93 | *dst = *src as f64; 94 | } 95 | }}; 96 | } 97 | 98 | macro_rules! tp_generic_impl { 99 | ( $interpolators:expr ) => {{ 100 | assert!(src.channels() == $interpolators.len() && src.channels() == peaks.len()); 101 | for (c, (interpolator, channel_peak)) in 102 | Iterator::zip($interpolators.iter_mut(), peaks.iter_mut()).enumerate() 103 | { 104 | src.foreach_sample(c, move |s| { 105 | for [new_sample] in &interpolator.interpolate([s.to_sample::()]) { 106 | let new_sample = new_sample.abs() as f64; 107 | if new_sample > *channel_peak { 108 | *channel_peak = new_sample; 109 | } 110 | } 111 | }); 112 | } 113 | }}; 114 | } 115 | 116 | match self { 117 | Mono2F(interpolator) => tp_specialized_impl!(1, interpolator), 118 | Stereo2F(interpolator) => tp_specialized_impl!(2, interpolator), 119 | Quad2F(interpolator) => tp_specialized_impl!(4, interpolator), 120 | Surround2F(interpolator) => tp_specialized_impl!(6, interpolator), 121 | OctoSurround2F(interpolator) => tp_specialized_impl!(8, interpolator), 122 | Mono4F(interpolator) => tp_specialized_impl!(1, interpolator), 123 | Stereo4F(interpolator) => tp_specialized_impl!(2, interpolator), 124 | Quad4F(interpolator) => tp_specialized_impl!(4, interpolator), 125 | Surround4F(interpolator) => tp_specialized_impl!(6, interpolator), 126 | OctoSurround4F(interpolator) => tp_specialized_impl!(8, interpolator), 127 | Generic2F(interpolators) => tp_generic_impl!(interpolators), 128 | Generic4F(interpolators) => tp_generic_impl!(interpolators), 129 | } 130 | } 131 | 132 | fn reset(&mut self) { 133 | match self { 134 | Mono2F(interpolator) => interpolator.reset(), 135 | Stereo2F(interpolator) => interpolator.reset(), 136 | Quad2F(interpolator) => interpolator.reset(), 137 | Surround2F(interpolator) => interpolator.reset(), 138 | OctoSurround2F(interpolator) => interpolator.reset(), 139 | Mono4F(interpolator) => interpolator.reset(), 140 | Stereo4F(interpolator) => interpolator.reset(), 141 | Quad4F(interpolator) => interpolator.reset(), 142 | Surround4F(interpolator) => interpolator.reset(), 143 | OctoSurround4F(interpolator) => interpolator.reset(), 144 | Generic2F(interpolators) => interpolators.iter_mut().for_each(InterpF::reset), 145 | Generic4F(interpolators) => interpolators.iter_mut().for_each(InterpF::reset), 146 | } 147 | } 148 | } 149 | 150 | /// True peak measurement. 151 | #[derive(Debug)] 152 | pub struct TruePeak { 153 | /// Interpolator/resampler. 154 | interp: UpsamplingScanner, 155 | } 156 | 157 | impl TruePeak { 158 | pub fn new(rate: u32, channels: u32) -> Option { 159 | UpsamplingScanner::new(rate, channels).map(|interp| Self { interp }) 160 | } 161 | 162 | pub fn reset(&mut self) { 163 | self.interp.reset(); 164 | } 165 | 166 | pub fn check_true_peak<'a, T: Sample + 'a, S: crate::Samples<'a, T>>( 167 | &mut self, 168 | src: S, 169 | peaks: &mut [f64], 170 | ) { 171 | self.interp.check_true_peak(src, peaks) 172 | } 173 | 174 | pub fn seed<'a, T: Sample + 'a, S: crate::Samples<'a, T>>(&mut self, src: S) { 175 | let mut true_peaks: SmallVec<[f64; 16]> = smallvec![0.0; src.channels()]; 176 | self.interp.check_true_peak(src, &mut true_peaks) 177 | } 178 | } 179 | 180 | #[cfg(feature = "c-tests")] 181 | use std::os::raw::c_void; 182 | 183 | #[cfg(feature = "c-tests")] 184 | extern "C" { 185 | pub fn true_peak_create_c(rate: u32, channels: u32) -> *mut c_void; 186 | pub fn true_peak_check_short_c( 187 | tp: *mut c_void, 188 | frames: usize, 189 | src: *const i16, 190 | peaks: *mut f64, 191 | ); 192 | pub fn true_peak_check_int_c(tp: *mut c_void, frames: usize, src: *const i32, peaks: *mut f64); 193 | pub fn true_peak_check_float_c( 194 | tp: *mut c_void, 195 | frames: usize, 196 | src: *const f32, 197 | peaks: *mut f64, 198 | ); 199 | pub fn true_peak_check_double_c( 200 | tp: *mut c_void, 201 | frames: usize, 202 | src: *const f64, 203 | peaks: *mut f64, 204 | ); 205 | pub fn true_peak_destroy_c(tp: *mut c_void); 206 | } 207 | 208 | #[cfg(feature = "c-tests")] 209 | #[cfg(test)] 210 | mod tests { 211 | use super::*; 212 | use crate::tests::Signal; 213 | use float_eq::assert_float_eq; 214 | use quickcheck_macros::quickcheck; 215 | 216 | #[quickcheck] 217 | fn compare_c_impl_i16(signal: Signal) -> quickcheck::TestResult { 218 | if signal.rate >= 192_000 { 219 | return quickcheck::TestResult::discard(); 220 | } 221 | 222 | // Maximum of 400ms but our input is up to 5000ms, so distribute it evenly 223 | // by shrinking accordingly. 224 | let len = signal.data.len() / signal.channels as usize; 225 | let len = std::cmp::min(2 * len / 25, 4 * ((signal.rate as usize + 5) / 10)); 226 | 227 | let mut peaks = vec![0.0f64; signal.channels as usize]; 228 | let mut peaks_c = vec![0.0f64; signal.channels as usize]; 229 | 230 | { 231 | let mut tp = TruePeak::new(signal.rate, signal.channels).unwrap(); 232 | tp.check_true_peak( 233 | crate::Interleaved::new( 234 | &signal.data[0..(len * signal.channels as usize)], 235 | signal.channels as usize, 236 | ) 237 | .unwrap(), 238 | &mut peaks, 239 | ); 240 | } 241 | 242 | unsafe { 243 | let tp = true_peak_create_c(signal.rate, signal.channels); 244 | assert!(!tp.is_null()); 245 | true_peak_check_short_c(tp, len, signal.data.as_ptr(), peaks_c.as_mut_ptr()); 246 | true_peak_destroy_c(tp); 247 | } 248 | 249 | for (i, (r, c)) in peaks.iter().zip(peaks_c.iter()).enumerate() { 250 | assert_float_eq!( 251 | *r, 252 | *c, 253 | // For a performance-boost, interpolation-filter is defined as f32, causing lower precision 254 | abs <= 0.000004, 255 | "Rust and C implementation differ at channel {}", 256 | i, 257 | ); 258 | } 259 | 260 | quickcheck::TestResult::passed() 261 | } 262 | 263 | #[quickcheck] 264 | fn compare_c_impl_i32(signal: Signal) -> quickcheck::TestResult { 265 | if signal.rate >= 192_000 { 266 | return quickcheck::TestResult::discard(); 267 | } 268 | 269 | // Maximum of 400ms but our input is up to 5000ms, so distribute it evenly 270 | // by shrinking accordingly. 271 | let len = signal.data.len() / signal.channels as usize; 272 | let len = std::cmp::min(2 * len / 25, 4 * ((signal.rate as usize + 5) / 10)); 273 | 274 | let mut peaks = vec![0.0f64; signal.channels as usize]; 275 | let mut peaks_c = vec![0.0f64; signal.channels as usize]; 276 | 277 | { 278 | let mut tp = TruePeak::new(signal.rate, signal.channels).unwrap(); 279 | tp.check_true_peak( 280 | crate::Interleaved::new( 281 | &signal.data[0..(len * signal.channels as usize)], 282 | signal.channels as usize, 283 | ) 284 | .unwrap(), 285 | &mut peaks, 286 | ); 287 | } 288 | 289 | unsafe { 290 | let tp = true_peak_create_c(signal.rate, signal.channels); 291 | assert!(!tp.is_null()); 292 | true_peak_check_int_c(tp, len, signal.data.as_ptr(), peaks_c.as_mut_ptr()); 293 | true_peak_destroy_c(tp); 294 | } 295 | 296 | for (i, (r, c)) in peaks.iter().zip(peaks_c.iter()).enumerate() { 297 | assert_float_eq!( 298 | *r, 299 | *c, 300 | // For a performance-boost, interpolation-filter is defined as f32, causing lower precision 301 | abs <= 0.000004, 302 | "Rust and C implementation differ at channel {}", 303 | i 304 | ); 305 | } 306 | 307 | quickcheck::TestResult::passed() 308 | } 309 | 310 | #[quickcheck] 311 | fn compare_c_impl_f32(signal: Signal) -> quickcheck::TestResult { 312 | if signal.rate >= 192_000 { 313 | return quickcheck::TestResult::discard(); 314 | } 315 | 316 | // Maximum of 400ms but our input is up to 5000ms, so distribute it evenly 317 | // by shrinking accordingly. 318 | let len = signal.data.len() / signal.channels as usize; 319 | let len = std::cmp::min(2 * len / 25, 4 * ((signal.rate as usize + 5) / 10)); 320 | 321 | let mut peaks = vec![0.0f64; signal.channels as usize]; 322 | let mut peaks_c = vec![0.0f64; signal.channels as usize]; 323 | 324 | { 325 | let mut tp = TruePeak::new(signal.rate, signal.channels).unwrap(); 326 | tp.check_true_peak( 327 | crate::Interleaved::new( 328 | &signal.data[0..(len * signal.channels as usize)], 329 | signal.channels as usize, 330 | ) 331 | .unwrap(), 332 | &mut peaks, 333 | ); 334 | } 335 | 336 | unsafe { 337 | let tp = true_peak_create_c(signal.rate, signal.channels); 338 | assert!(!tp.is_null()); 339 | true_peak_check_float_c(tp, len, signal.data.as_ptr(), peaks_c.as_mut_ptr()); 340 | true_peak_destroy_c(tp); 341 | } 342 | 343 | for (i, (r, c)) in peaks.iter().zip(peaks_c.iter()).enumerate() { 344 | assert_float_eq!( 345 | *r, 346 | *c, 347 | // For a performance-boost, interpolation-filter is defined as f32, causing lower precision 348 | abs <= 0.000004, 349 | "Rust and C implementation differ at channel {}", 350 | i 351 | ); 352 | } 353 | 354 | quickcheck::TestResult::passed() 355 | } 356 | 357 | #[quickcheck] 358 | fn compare_c_impl_f64(signal: Signal) -> quickcheck::TestResult { 359 | if signal.rate >= 192_000 { 360 | return quickcheck::TestResult::discard(); 361 | } 362 | 363 | // Maximum of 400ms but our input is up to 5000ms, so distribute it evenly 364 | // by shrinking accordingly. 365 | let len = signal.data.len() / signal.channels as usize; 366 | let len = std::cmp::min(2 * len / 25, 4 * ((signal.rate as usize + 5) / 10)); 367 | 368 | let mut peaks = vec![0.0f64; signal.channels as usize]; 369 | let mut peaks_c = vec![0.0f64; signal.channels as usize]; 370 | 371 | { 372 | let mut tp = TruePeak::new(signal.rate, signal.channels).unwrap(); 373 | tp.check_true_peak( 374 | crate::Interleaved::new( 375 | &signal.data[0..(len * signal.channels as usize)], 376 | signal.channels as usize, 377 | ) 378 | .unwrap(), 379 | &mut peaks, 380 | ); 381 | } 382 | 383 | unsafe { 384 | let tp = true_peak_create_c(signal.rate, signal.channels); 385 | assert!(!tp.is_null()); 386 | true_peak_check_double_c(tp, len, signal.data.as_ptr(), peaks_c.as_mut_ptr()); 387 | true_peak_destroy_c(tp); 388 | } 389 | 390 | for (i, (r, c)) in peaks.iter().zip(peaks_c.iter()).enumerate() { 391 | assert_float_eq!( 392 | *r, 393 | *c, 394 | // For a performance-boost, interpolation-filter is defined as f32, causing lower precision 395 | abs <= 0.000004, 396 | "Rust and C implementation differ at channel {}", 397 | i 398 | ); 399 | } 400 | 401 | quickcheck::TestResult::passed() 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Jan Kokemüller 2 | // Copyright (c) 2020 Sebastian Dröge 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | use dasp_frame::Frame; 23 | 24 | /// Convert linear energy to logarithmic loudness. 25 | pub fn energy_to_loudness(energy: f64) -> f64 { 26 | // The non-test version is faster and more accurate but gives 27 | // slightly different results than the C version and fails the 28 | // tests because of that. 29 | #[cfg(test)] 30 | { 31 | 10.0 * (f64::ln(energy) / std::f64::consts::LN_10) - 0.691 32 | } 33 | #[cfg(not(test))] 34 | { 35 | 10.0 * f64::log10(energy) - 0.691 36 | } 37 | } 38 | 39 | /// Trait for abstracting over interleaved and planar samples. 40 | pub trait Samples<'a, S: Sample + 'a>: Sized { 41 | /// Call the given closure for each sample of the given channel. 42 | // FIXME: Workaround for TrustedLen / TrustedRandomAccess being unstable 43 | // and because of that we wouldn't get nice optimizations 44 | fn foreach_sample(&self, channel: usize, func: impl FnMut(&'a S)); 45 | 46 | /// Call the given closure for each sample of the given channel. 47 | // FIXME: Workaround for TrustedLen / TrustedRandomAccess being unstable 48 | // and because of that we wouldn't get nice optimizations 49 | fn foreach_sample_zipped( 50 | &self, 51 | channel: usize, 52 | iter: impl Iterator, 53 | func: impl FnMut(&'a S, U), 54 | ); 55 | 56 | fn foreach_frame>(&self, func: impl FnMut(F)); 57 | 58 | /// Number of frames. 59 | fn frames(&self) -> usize; 60 | 61 | /// Number of channels. 62 | fn channels(&self) -> usize; 63 | 64 | /// Split into two at the given sample. 65 | fn split_at(self, sample: usize) -> (Self, Self); 66 | } 67 | 68 | /// Struct representing interleaved samples. 69 | pub struct Interleaved<'a, S> { 70 | /// Interleaved sample data. 71 | data: &'a [S], 72 | /// Number of channels. 73 | channels: usize, 74 | } 75 | 76 | impl<'a, S> Interleaved<'a, S> { 77 | /// Create a new wrapper around the interleaved channels and do a sanity check. 78 | pub fn new(data: &'a [S], channels: usize) -> Result { 79 | if channels == 0 { 80 | return Err(crate::Error::NoMem); 81 | } 82 | 83 | if data.len() % channels != 0 { 84 | return Err(crate::Error::NoMem); 85 | } 86 | 87 | Ok(Interleaved { data, channels }) 88 | } 89 | } 90 | 91 | impl<'a, S: Sample> Samples<'a, S> for Interleaved<'a, S> { 92 | #[inline] 93 | fn foreach_sample(&self, channel: usize, mut func: impl FnMut(&'a S)) { 94 | assert!(channel < self.channels); 95 | 96 | for v in self.data.chunks_exact(self.channels) { 97 | func(&v[channel]) 98 | } 99 | } 100 | 101 | #[inline] 102 | fn foreach_sample_zipped( 103 | &self, 104 | channel: usize, 105 | iter: impl Iterator, 106 | mut func: impl FnMut(&'a S, U), 107 | ) { 108 | assert!(channel < self.channels); 109 | 110 | for (v, u) in Iterator::zip(self.data.chunks_exact(self.channels), iter) { 111 | func(&v[channel], u) 112 | } 113 | } 114 | 115 | #[inline] 116 | fn foreach_frame>(&self, mut func: impl FnMut(F)) { 117 | assert_eq!(F::CHANNELS, self.channels); 118 | for f in self.data.chunks_exact(self.channels) { 119 | func(F::from_samples(&mut f.iter().copied()).unwrap()); 120 | } 121 | } 122 | 123 | #[inline] 124 | fn frames(&self) -> usize { 125 | self.data.len() / self.channels 126 | } 127 | 128 | #[inline] 129 | fn channels(&self) -> usize { 130 | self.channels 131 | } 132 | 133 | #[inline] 134 | fn split_at(self, sample: usize) -> (Self, Self) { 135 | assert!(sample * self.channels <= self.data.len()); 136 | 137 | let (fst, snd) = self.data.split_at(sample * self.channels); 138 | ( 139 | Interleaved { 140 | data: fst, 141 | channels: self.channels, 142 | }, 143 | Interleaved { 144 | data: snd, 145 | channels: self.channels, 146 | }, 147 | ) 148 | } 149 | } 150 | 151 | /// Struct representing planar samples. 152 | pub struct Planar<'a, S> { 153 | data: &'a [&'a [S]], 154 | start: usize, 155 | end: usize, 156 | } 157 | 158 | impl<'a, S> Planar<'a, S> { 159 | /// Create a new wrapper around the planar channels and do a sanity check. 160 | pub fn new(data: &'a [&'a [S]]) -> Result { 161 | if data.is_empty() { 162 | return Err(crate::Error::NoMem); 163 | } 164 | 165 | if data.iter().any(|d| data[0].len() != d.len()) { 166 | return Err(crate::Error::NoMem); 167 | } 168 | 169 | Ok(Planar { 170 | data, 171 | start: 0, 172 | end: data[0].len(), 173 | }) 174 | } 175 | } 176 | 177 | impl<'a, S: Sample> Samples<'a, S> for Planar<'a, S> { 178 | #[inline] 179 | fn foreach_sample(&self, channel: usize, mut func: impl FnMut(&'a S)) { 180 | assert!(channel < self.data.len()); 181 | 182 | for v in &self.data[channel][self.start..self.end] { 183 | func(v) 184 | } 185 | } 186 | 187 | #[inline] 188 | fn foreach_sample_zipped( 189 | &self, 190 | channel: usize, 191 | iter: impl Iterator, 192 | mut func: impl FnMut(&'a S, U), 193 | ) { 194 | assert!(channel < self.data.len()); 195 | 196 | for (v, u) in Iterator::zip(self.data[channel][self.start..self.end].iter(), iter) { 197 | func(v, u) 198 | } 199 | } 200 | 201 | #[inline] 202 | fn foreach_frame>(&self, mut func: impl FnMut(F)) { 203 | let channels = self.data.len(); 204 | assert_eq!(F::CHANNELS, channels); 205 | for f in self.start..self.end { 206 | func(F::from_fn(|c| self.data[c][f])); 207 | } 208 | } 209 | 210 | #[inline] 211 | fn frames(&self) -> usize { 212 | self.end - self.start 213 | } 214 | 215 | #[inline] 216 | fn channels(&self) -> usize { 217 | self.data.len() 218 | } 219 | 220 | #[inline] 221 | fn split_at(self, sample: usize) -> (Self, Self) { 222 | assert!(self.start + sample <= self.end); 223 | 224 | ( 225 | Planar { 226 | data: self.data, 227 | start: self.start, 228 | end: self.start + sample, 229 | }, 230 | Planar { 231 | data: self.data, 232 | start: self.start + sample, 233 | end: self.end, 234 | }, 235 | ) 236 | } 237 | } 238 | 239 | pub trait Sample: 240 | dasp_sample::Sample + dasp_sample::Duplex + dasp_sample::Duplex 241 | { 242 | const MAX_AMPLITUDE: f64; 243 | 244 | fn as_f64_raw(self) -> f64; 245 | } 246 | 247 | impl Sample for f32 { 248 | const MAX_AMPLITUDE: f64 = 1.0; 249 | 250 | #[inline(always)] 251 | fn as_f64_raw(self) -> f64 { 252 | self as f64 253 | } 254 | } 255 | impl Sample for f64 { 256 | const MAX_AMPLITUDE: f64 = 1.0; 257 | 258 | #[inline(always)] 259 | fn as_f64_raw(self) -> f64 { 260 | self 261 | } 262 | } 263 | impl Sample for i16 { 264 | const MAX_AMPLITUDE: f64 = -(Self::MIN as f64); 265 | 266 | #[inline(always)] 267 | fn as_f64_raw(self) -> f64 { 268 | self as f64 269 | } 270 | } 271 | impl Sample for i32 { 272 | const MAX_AMPLITUDE: f64 = -(Self::MIN as f64); 273 | 274 | #[inline(always)] 275 | fn as_f64_raw(self) -> f64 { 276 | self as f64 277 | } 278 | } 279 | 280 | /// An extension-trait to accumulate samples into a frame 281 | pub trait FrameAccumulator: Frame { 282 | fn scale_add(&mut self, other: &Self, coeff: f32); 283 | fn retain_max_samples(&mut self, other: &Self); 284 | } 285 | 286 | impl FrameAccumulator for F 287 | where 288 | S: SampleAccumulator + std::fmt::Debug, 289 | F: IndexMut, 290 | { 291 | #[inline(always)] 292 | fn scale_add(&mut self, other: &Self, coeff: f32) { 293 | for i in 0..Self::CHANNELS { 294 | self.index_mut(i).scale_add(*other.index(i), coeff); 295 | } 296 | } 297 | 298 | fn retain_max_samples(&mut self, other: &Self) { 299 | for i in 0..Self::CHANNELS { 300 | let this = self.index_mut(i); 301 | let other = other.index(i); 302 | if *other > *this { 303 | *this = *other; 304 | } 305 | } 306 | } 307 | } 308 | 309 | // Required since std::ops::IndexMut seem to not be implemented for arrays 310 | // making FrameAcc hard to implement for auto-vectorization 311 | // IndexMut seems to be coming to stdlib, when https://github.com/rust-lang/rust/pull/74989 312 | // implemented, this trait can be removed 313 | pub trait IndexMut { 314 | type Target; 315 | fn index_mut(&mut self, i: usize) -> &mut Self::Target; 316 | fn index(&self, i: usize) -> &Self::Target; 317 | } 318 | 319 | macro_rules! index_mut_impl { 320 | ( $channels:expr ) => { 321 | impl IndexMut for [T; $channels] { 322 | type Target = T; 323 | #[inline(always)] 324 | fn index_mut(&mut self, i: usize) -> &mut Self::Target { 325 | &mut self[i] 326 | } 327 | #[inline(always)] 328 | fn index(&self, i: usize) -> &Self::Target { 329 | &self[i] 330 | } 331 | } 332 | }; 333 | } 334 | 335 | index_mut_impl!(1); 336 | index_mut_impl!(2); 337 | index_mut_impl!(4); 338 | index_mut_impl!(6); 339 | index_mut_impl!(8); 340 | 341 | pub trait SampleAccumulator: Sample { 342 | fn scale_add(&mut self, other: Self, coeff: f32); 343 | } 344 | 345 | impl SampleAccumulator for f32 { 346 | #[inline(always)] 347 | fn scale_add(&mut self, other: Self, coeff: f32) { 348 | #[cfg(feature = "precision-true-peak")] 349 | { 350 | *self = other.mul_add(coeff, *self); 351 | } 352 | #[cfg(not(feature = "precision-true-peak"))] 353 | { 354 | *self += other * coeff 355 | } 356 | } 357 | } 358 | 359 | #[cfg(test)] 360 | pub mod tests { 361 | use dasp_sample::{FromSample, Sample}; 362 | 363 | #[derive(Clone, Debug)] 364 | pub struct Signal> { 365 | pub data: Vec, 366 | pub channels: u32, 367 | pub rate: u32, 368 | } 369 | 370 | impl + quickcheck::Arbitrary> quickcheck::Arbitrary for Signal { 371 | fn arbitrary(g: &mut G) -> Self { 372 | use rand::Rng; 373 | 374 | let channels = g.gen_range(1, 16); 375 | let rate = g.gen_range(16_000, 224_000); 376 | let num_frames = (rate as f64 * g.gen_range(0.0, 5.0)) as usize; 377 | 378 | let max = g.gen_range(0.0, 1.0); 379 | let freqs = [ 380 | g.gen_range(20.0, 16_000.0), 381 | g.gen_range(20.0, 16_000.0), 382 | g.gen_range(20.0, 16_000.0), 383 | g.gen_range(20.0, 16_000.0), 384 | ]; 385 | let volumes = [ 386 | g.gen_range(0.0, 1.0), 387 | g.gen_range(0.0, 1.0), 388 | g.gen_range(0.0, 1.0), 389 | g.gen_range(0.0, 1.0), 390 | ]; 391 | let volume_scale = 1.0 / volumes.iter().sum::(); 392 | let mut accumulators = [0.0; 4]; 393 | let steps = [ 394 | 2.0 * std::f32::consts::PI * freqs[0] / rate as f32, 395 | 2.0 * std::f32::consts::PI * freqs[1] / rate as f32, 396 | 2.0 * std::f32::consts::PI * freqs[2] / rate as f32, 397 | 2.0 * std::f32::consts::PI * freqs[3] / rate as f32, 398 | ]; 399 | 400 | let mut data = vec![S::from_sample(0.0f32); num_frames * channels as usize]; 401 | for frame in data.chunks_exact_mut(channels as usize) { 402 | let val = max 403 | * (f32::sin(accumulators[0]) * volumes[0] 404 | + f32::sin(accumulators[1]) * volumes[1] 405 | + f32::sin(accumulators[2]) * volumes[2] 406 | + f32::sin(accumulators[3]) * volumes[3]) 407 | / volume_scale; 408 | 409 | for sample in frame.iter_mut() { 410 | *sample = S::from_sample(val); 411 | } 412 | 413 | for (acc, step) in accumulators.iter_mut().zip(steps.iter()) { 414 | *acc += step; 415 | } 416 | } 417 | 418 | Signal { 419 | data, 420 | channels, 421 | rate, 422 | } 423 | } 424 | 425 | fn shrink(&self) -> Box> { 426 | SignalShrinker::boxed(self.clone()) 427 | } 428 | } 429 | 430 | struct SignalShrinker> { 431 | seed: Signal, 432 | /// How many elements to take 433 | size: usize, 434 | /// Whether we tried with one channel already 435 | tried_one_channel: bool, 436 | } 437 | 438 | impl + quickcheck::Arbitrary> SignalShrinker { 439 | fn boxed(seed: Signal) -> Box>> { 440 | let channels = seed.channels; 441 | Box::new(SignalShrinker { 442 | seed, 443 | size: 0, 444 | tried_one_channel: channels == 1, 445 | }) 446 | } 447 | } 448 | 449 | impl Iterator for SignalShrinker 450 | where 451 | A: FromSample + quickcheck::Arbitrary, 452 | { 453 | type Item = Signal; 454 | fn next(&mut self) -> Option> { 455 | if self.size < self.seed.data.len() { 456 | // Generate a smaller vector by removing size elements 457 | let xs1 = if self.tried_one_channel { 458 | Vec::from(&self.seed.data[..self.size]) 459 | } else { 460 | self.seed 461 | .data 462 | .iter() 463 | .cloned() 464 | .step_by(self.seed.channels as usize) 465 | .take(self.size) 466 | .collect() 467 | }; 468 | 469 | if self.size == 0 { 470 | self.size = if self.tried_one_channel { 471 | self.seed.channels as usize 472 | } else { 473 | 1 474 | }; 475 | } else { 476 | self.size *= 2; 477 | } 478 | 479 | Some(Signal { 480 | data: xs1, 481 | channels: if self.tried_one_channel { 482 | self.seed.channels 483 | } else { 484 | 1 485 | }, 486 | rate: self.seed.rate, 487 | }) 488 | } else if !self.tried_one_channel { 489 | self.tried_one_channel = true; 490 | self.size = 0; 491 | self.next() 492 | } else { 493 | None 494 | } 495 | } 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /assets/ebur128.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #ifndef EBUR128_H_ 4 | #define EBUR128_H_ 5 | 6 | /** \file ebur128.h 7 | * \brief libebur128 - a library for loudness measurement according to 8 | * the EBU R128 standard. 9 | */ 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | #define EBUR128_VERSION_MAJOR 1 16 | #define EBUR128_VERSION_MINOR 2 17 | #define EBUR128_VERSION_PATCH 6 18 | 19 | #include /* for size_t */ 20 | 21 | /** \enum channel 22 | * Use these values when setting the channel map with ebur128_set_channel(). 23 | * See definitions in ITU R-REC-BS 1770-4 24 | */ 25 | enum channel { 26 | EBUR128_UNUSED = 0, /**< unused channel (for example LFE channel) */ 27 | EBUR128_LEFT = 1, 28 | EBUR128_Mp030 = 1, /**< itu M+030 */ 29 | EBUR128_RIGHT = 2, 30 | EBUR128_Mm030 = 2, /**< itu M-030 */ 31 | EBUR128_CENTER = 3, 32 | EBUR128_Mp000 = 3, /**< itu M+000 */ 33 | EBUR128_LEFT_SURROUND = 4, 34 | EBUR128_Mp110 = 4, /**< itu M+110 */ 35 | EBUR128_RIGHT_SURROUND = 5, 36 | EBUR128_Mm110 = 5, /**< itu M-110 */ 37 | EBUR128_DUAL_MONO, /**< a channel that is counted twice */ 38 | EBUR128_MpSC, /**< itu M+SC */ 39 | EBUR128_MmSC, /**< itu M-SC */ 40 | EBUR128_Mp060, /**< itu M+060 */ 41 | EBUR128_Mm060, /**< itu M-060 */ 42 | EBUR128_Mp090, /**< itu M+090 */ 43 | EBUR128_Mm090, /**< itu M-090 */ 44 | EBUR128_Mp135, /**< itu M+135 */ 45 | EBUR128_Mm135, /**< itu M-135 */ 46 | EBUR128_Mp180, /**< itu M+180 */ 47 | EBUR128_Up000, /**< itu U+000 */ 48 | EBUR128_Up030, /**< itu U+030 */ 49 | EBUR128_Um030, /**< itu U-030 */ 50 | EBUR128_Up045, /**< itu U+045 */ 51 | EBUR128_Um045, /**< itu U-030 */ 52 | EBUR128_Up090, /**< itu U+090 */ 53 | EBUR128_Um090, /**< itu U-090 */ 54 | EBUR128_Up110, /**< itu U+110 */ 55 | EBUR128_Um110, /**< itu U-110 */ 56 | EBUR128_Up135, /**< itu U+135 */ 57 | EBUR128_Um135, /**< itu U-135 */ 58 | EBUR128_Up180, /**< itu U+180 */ 59 | EBUR128_Tp000, /**< itu T+000 */ 60 | EBUR128_Bp000, /**< itu B+000 */ 61 | EBUR128_Bp045, /**< itu B+045 */ 62 | EBUR128_Bm045 /**< itu B-045 */ 63 | }; 64 | 65 | /** \enum error 66 | * Error return values. 67 | */ 68 | enum error { 69 | EBUR128_SUCCESS = 0, 70 | EBUR128_ERROR_NOMEM, 71 | EBUR128_ERROR_INVALID_MODE, 72 | EBUR128_ERROR_INVALID_CHANNEL_INDEX, 73 | EBUR128_ERROR_NO_CHANGE 74 | }; 75 | 76 | /** \enum mode 77 | * Use these values in ebur128_init (or'ed). Try to use the lowest possible 78 | * modes that suit your needs, as performance will be better. 79 | */ 80 | enum mode { 81 | /** can call ebur128_loudness_momentary */ 82 | EBUR128_MODE_M = (1 << 0), 83 | /** can call ebur128_loudness_shortterm */ 84 | EBUR128_MODE_S = (1 << 1) | EBUR128_MODE_M, 85 | /** can call ebur128_loudness_global_* and ebur128_relative_threshold */ 86 | EBUR128_MODE_I = (1 << 2) | EBUR128_MODE_M, 87 | /** can call ebur128_loudness_range */ 88 | EBUR128_MODE_LRA = (1 << 3) | EBUR128_MODE_S, 89 | /** can call ebur128_sample_peak */ 90 | EBUR128_MODE_SAMPLE_PEAK = (1 << 4) | EBUR128_MODE_M, 91 | /** can call ebur128_true_peak */ 92 | EBUR128_MODE_TRUE_PEAK = (1 << 5) | EBUR128_MODE_M 93 | | EBUR128_MODE_SAMPLE_PEAK, 94 | /** uses histogram algorithm to calculate loudness */ 95 | EBUR128_MODE_HISTOGRAM = (1 << 6) 96 | }; 97 | 98 | /** forward declaration of ebur128_state_internal */ 99 | struct ebur128_state_internal; 100 | 101 | /** \brief Contains information about the state of a loudness measurement. 102 | * 103 | * You should not need to modify this struct directly. 104 | */ 105 | typedef struct { 106 | int mode; /**< The current mode. */ 107 | unsigned int channels; /**< The number of channels. */ 108 | unsigned long samplerate; /**< The sample rate. */ 109 | struct ebur128_state_internal* d; /**< Internal state. */ 110 | } ebur128_state; 111 | 112 | /** \brief Get library version number. Do not pass null pointers here. 113 | * 114 | * @param major major version number of library 115 | * @param minor minor version number of library 116 | * @param patch patch version number of library 117 | */ 118 | void ebur128_get_version(int* major, int* minor, int* patch); 119 | 120 | /** \brief Initialize library state. 121 | * 122 | * @param channels the number of channels. 123 | * @param samplerate the sample rate. 124 | * @param mode see the mode enum for possible values. 125 | * @return an initialized library state, or NULL on error. 126 | */ 127 | ebur128_state* ebur128_init(unsigned int channels, 128 | unsigned long samplerate, 129 | int mode); 130 | 131 | /** \brief Destroy library state. 132 | * 133 | * @param st pointer to a library state. 134 | */ 135 | void ebur128_destroy(ebur128_state** st); 136 | 137 | /** \brief Set channel type. 138 | * 139 | * The default is: 140 | * - 0 -> EBUR128_LEFT 141 | * - 1 -> EBUR128_RIGHT 142 | * - 2 -> EBUR128_CENTER 143 | * - 3 -> EBUR128_UNUSED 144 | * - 4 -> EBUR128_LEFT_SURROUND 145 | * - 5 -> EBUR128_RIGHT_SURROUND 146 | * 147 | * @param st library state. 148 | * @param channel_number zero based channel index. 149 | * @param value channel type from the "channel" enum. 150 | * @return 151 | * - EBUR128_SUCCESS on success. 152 | * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. 153 | */ 154 | int ebur128_set_channel(ebur128_state* st, 155 | unsigned int channel_number, 156 | int value); 157 | 158 | /** \brief Change library parameters. 159 | * 160 | * Note that the channel map will be reset when setting a different number of 161 | * channels. The current unfinished block will be lost. 162 | * 163 | * @param st library state. 164 | * @param channels new number of channels. 165 | * @param samplerate new sample rate. 166 | * @return 167 | * - EBUR128_SUCCESS on success. 168 | * - EBUR128_ERROR_NOMEM on memory allocation error. The state will be 169 | * invalid and must be destroyed. 170 | * - EBUR128_ERROR_NO_CHANGE if channels and sample rate were not changed. 171 | */ 172 | int ebur128_change_parameters(ebur128_state* st, 173 | unsigned int channels, 174 | unsigned long samplerate); 175 | 176 | /** \brief Set the maximum window duration. 177 | * 178 | * Set the maximum duration that will be used for ebur128_loudness_window(). 179 | * Note that this destroys the current content of the audio buffer. 180 | * 181 | * @param st library state. 182 | * @param window duration of the window in ms. 183 | * @return 184 | * - EBUR128_SUCCESS on success. 185 | * - EBUR128_ERROR_NOMEM on memory allocation error. The state will be 186 | * invalid and must be destroyed. 187 | * - EBUR128_ERROR_NO_CHANGE if window duration not changed. 188 | */ 189 | int ebur128_set_max_window(ebur128_state* st, unsigned long window); 190 | 191 | /** \brief Set the maximum history. 192 | * 193 | * Set the maximum history that will be stored for loudness integration. 194 | * More history provides more accurate results, but requires more resources. 195 | * 196 | * Applies to ebur128_loudness_range() and ebur128_loudness_global() when 197 | * EBUR128_MODE_HISTOGRAM is not set. 198 | * 199 | * Default is ULONG_MAX (at least ~50 days). 200 | * Minimum is 3000ms for EBUR128_MODE_LRA and 400ms for EBUR128_MODE_M. 201 | * 202 | * @param st library state. 203 | * @param history duration of history in ms. 204 | * @return 205 | * - EBUR128_SUCCESS on success. 206 | * - EBUR128_ERROR_NO_CHANGE if history not changed. 207 | */ 208 | int ebur128_set_max_history(ebur128_state* st, unsigned long history); 209 | 210 | /** \brief Add frames to be processed. 211 | * 212 | * @param st library state. 213 | * @param src array of source frames. Channels must be interleaved. 214 | * @param frames number of frames. Not number of samples! 215 | * @return 216 | * - EBUR128_SUCCESS on success. 217 | * - EBUR128_ERROR_NOMEM on memory allocation error. 218 | */ 219 | int ebur128_add_frames_short(ebur128_state* st, 220 | const short* src, 221 | size_t frames); 222 | /** \brief See \ref ebur128_add_frames_short */ 223 | int ebur128_add_frames_int(ebur128_state* st, 224 | const int* src, 225 | size_t frames); 226 | /** \brief See \ref ebur128_add_frames_short */ 227 | int ebur128_add_frames_float(ebur128_state* st, 228 | const float* src, 229 | size_t frames); 230 | /** \brief See \ref ebur128_add_frames_short */ 231 | int ebur128_add_frames_double(ebur128_state* st, 232 | const double* src, 233 | size_t frames); 234 | 235 | /** \brief Get global integrated loudness in LUFS. 236 | * 237 | * @param st library state. 238 | * @param out integrated loudness in LUFS. -HUGE_VAL if result is negative 239 | * infinity. 240 | * @return 241 | * - EBUR128_SUCCESS on success. 242 | * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not been set. 243 | */ 244 | int ebur128_loudness_global(ebur128_state* st, double* out); 245 | /** \brief Get global integrated loudness in LUFS across multiple instances. 246 | * 247 | * @param sts array of library states. 248 | * @param size length of sts 249 | * @param out integrated loudness in LUFS. -HUGE_VAL if result is negative 250 | * infinity. 251 | * @return 252 | * - EBUR128_SUCCESS on success. 253 | * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not been set. 254 | */ 255 | int ebur128_loudness_global_multiple(ebur128_state** sts, 256 | size_t size, 257 | double* out); 258 | 259 | /** \brief Get momentary loudness (last 400ms) in LUFS. 260 | * 261 | * @param st library state. 262 | * @param out momentary loudness in LUFS. -HUGE_VAL if result is negative 263 | * infinity. 264 | * @return 265 | * - EBUR128_SUCCESS on success. 266 | */ 267 | int ebur128_loudness_momentary(ebur128_state* st, double* out); 268 | /** \brief Get short-term loudness (last 3s) in LUFS. 269 | * 270 | * @param st library state. 271 | * @param out short-term loudness in LUFS. -HUGE_VAL if result is negative 272 | * infinity. 273 | * @return 274 | * - EBUR128_SUCCESS on success. 275 | * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_S" has not been set. 276 | */ 277 | int ebur128_loudness_shortterm(ebur128_state* st, double* out); 278 | 279 | /** \brief Get loudness of the specified window in LUFS. 280 | * 281 | * window must not be larger than the current window set in st. 282 | * The current window can be changed by calling ebur128_set_max_window(). 283 | * 284 | * @param st library state. 285 | * @param window window in ms to calculate loudness. 286 | * @param out loudness in LUFS. -HUGE_VAL if result is negative infinity. 287 | * @return 288 | * - EBUR128_SUCCESS on success. 289 | * - EBUR128_ERROR_INVALID_MODE if window larger than current window in st. 290 | */ 291 | int ebur128_loudness_window(ebur128_state* st, 292 | unsigned long window, 293 | double* out); 294 | 295 | /** \brief Get loudness range (LRA) of programme in LU. 296 | * 297 | * Calculates loudness range according to EBU 3342. 298 | * 299 | * @param st library state. 300 | * @param out loudness range (LRA) in LU. Will not be changed in case of 301 | * error. EBUR128_ERROR_NOMEM or EBUR128_ERROR_INVALID_MODE will be 302 | * returned in this case. 303 | * @return 304 | * - EBUR128_SUCCESS on success. 305 | * - EBUR128_ERROR_NOMEM in case of memory allocation error. 306 | * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_LRA" has not been set. 307 | */ 308 | int ebur128_loudness_range(ebur128_state* st, double* out); 309 | /** \brief Get loudness range (LRA) in LU across multiple instances. 310 | * 311 | * Calculates loudness range according to EBU 3342. 312 | * 313 | * @param sts array of library states. 314 | * @param size length of sts 315 | * @param out loudness range (LRA) in LU. Will not be changed in case of 316 | * error. EBUR128_ERROR_NOMEM or EBUR128_ERROR_INVALID_MODE will be 317 | * returned in this case. 318 | * @return 319 | * - EBUR128_SUCCESS on success. 320 | * - EBUR128_ERROR_NOMEM in case of memory allocation error. 321 | * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_LRA" has not been set. 322 | */ 323 | int ebur128_loudness_range_multiple(ebur128_state** sts, 324 | size_t size, 325 | double* out); 326 | 327 | /** \brief Get maximum sample peak from all frames that have been processed. 328 | * 329 | * The equation to convert to dBFS is: 20 * log10(out) 330 | * 331 | * @param st library state 332 | * @param channel_number channel to analyse 333 | * @param out maximum sample peak in float format (1.0 is 0 dBFS) 334 | * @return 335 | * - EBUR128_SUCCESS on success. 336 | * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_SAMPLE_PEAK" has not 337 | * been set. 338 | * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. 339 | */ 340 | int ebur128_sample_peak(ebur128_state* st, 341 | unsigned int channel_number, 342 | double* out); 343 | 344 | /** \brief Get maximum sample peak from the last call to add_frames(). 345 | * 346 | * The equation to convert to dBFS is: 20 * log10(out) 347 | * 348 | * @param st library state 349 | * @param channel_number channel to analyse 350 | * @param out maximum sample peak in float format (1.0 is 0 dBFS) 351 | * @return 352 | * - EBUR128_SUCCESS on success. 353 | * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_SAMPLE_PEAK" has not 354 | * been set. 355 | * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. 356 | */ 357 | int ebur128_prev_sample_peak(ebur128_state* st, 358 | unsigned int channel_number, 359 | double* out); 360 | 361 | /** \brief Get maximum true peak from all frames that have been processed. 362 | * 363 | * Uses an implementation defined algorithm to calculate the true peak. Do not 364 | * try to compare resulting values across different versions of the library, 365 | * as the algorithm may change. 366 | * 367 | * The current implementation uses a custom polyphase FIR interpolator to 368 | * calculate true peak. Will oversample 4x for sample rates < 96000 Hz, 2x for 369 | * sample rates < 192000 Hz and leave the signal unchanged for 192000 Hz. 370 | * 371 | * The equation to convert to dBTP is: 20 * log10(out) 372 | * 373 | * @param st library state 374 | * @param channel_number channel to analyse 375 | * @param out maximum true peak in float format (1.0 is 0 dBTP) 376 | * @return 377 | * - EBUR128_SUCCESS on success. 378 | * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_TRUE_PEAK" has not 379 | * been set. 380 | * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. 381 | */ 382 | int ebur128_true_peak(ebur128_state* st, 383 | unsigned int channel_number, 384 | double* out); 385 | 386 | /** \brief Get maximum true peak from the last call to add_frames(). 387 | * 388 | * Uses an implementation defined algorithm to calculate the true peak. Do not 389 | * try to compare resulting values across different versions of the library, 390 | * as the algorithm may change. 391 | * 392 | * The current implementation uses a custom polyphase FIR interpolator to 393 | * calculate true peak. Will oversample 4x for sample rates < 96000 Hz, 2x for 394 | * sample rates < 192000 Hz and leave the signal unchanged for 192000 Hz. 395 | * 396 | * The equation to convert to dBTP is: 20 * log10(out) 397 | * 398 | * @param st library state 399 | * @param channel_number channel to analyse 400 | * @param out maximum true peak in float format (1.0 is 0 dBTP) 401 | * @return 402 | * - EBUR128_SUCCESS on success. 403 | * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_TRUE_PEAK" has not 404 | * been set. 405 | * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. 406 | */ 407 | int ebur128_prev_true_peak(ebur128_state* st, 408 | unsigned int channel_number, 409 | double* out); 410 | 411 | /** \brief Get relative threshold in LUFS. 412 | * 413 | * @param st library state 414 | * @param out relative threshold in LUFS. 415 | * @return 416 | * - EBUR128_SUCCESS on success. 417 | * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not 418 | * been set. 419 | */ 420 | int ebur128_relative_threshold(ebur128_state* st, double* out); 421 | 422 | #ifdef __cplusplus 423 | } 424 | #endif 425 | 426 | #endif /* EBUR128_H_ */ 427 | -------------------------------------------------------------------------------- /tests/reference_tests.rs: -------------------------------------------------------------------------------- 1 | // Tests can be downloaded from https://tech.ebu.ch/publications/ebu_loudness_test_set 2 | // and should be put into tests/reference_files 3 | // 4 | // Expected results are in 5 | // - https://tech.ebu.ch/docs/tech/tech3341.pdf 6 | // - https://tech.ebu.ch/docs/tech/tech3342.pdf 7 | 8 | macro_rules! read_samples { 9 | ($reader:expr, 16) => { 10 | $reader 11 | .into_samples::() 12 | .map(|s| s.expect("Failed to read samples from reference file")) 13 | .collect::>() 14 | }; 15 | ($reader:expr, 24) => { 16 | $reader 17 | .into_samples::() 18 | .map(|s| s.expect("Failed to read samples from reference file") << 8) 19 | .collect::>() 20 | }; 21 | } 22 | 23 | macro_rules! prepare_file { 24 | ($file_name:expr, $rate:expr, $bpp:tt, $mode:expr, $channel_map:expr) => {{ 25 | let input_path = { 26 | let mut r = std::path::PathBuf::new(); 27 | r.push(env!("CARGO_MANIFEST_DIR")); 28 | r.push("tests"); 29 | r.push("reference_files"); 30 | r.push($file_name); 31 | r 32 | }; 33 | 34 | if !input_path.exists() { 35 | panic!("Reference file {} not found", input_path.display()); 36 | } 37 | 38 | let mut e = ebur128::EbuR128::new($channel_map.len() as u32, $rate, $mode) 39 | .expect("Can't create EbuR128 instance"); 40 | e.set_channel_map(&$channel_map).unwrap(); 41 | 42 | let reader = hound::WavReader::open(&input_path).expect("Failed to read reference file"); 43 | let samples = read_samples!(reader, $bpp); 44 | 45 | (e, samples) 46 | }}; 47 | } 48 | 49 | macro_rules! add_samples { 50 | ($e:expr, $samples:expr, 16) => { 51 | $e.add_frames_i16(&*$samples).expect("Failed to analyze samples"); 52 | }; 53 | ($e:expr, $samples:expr, 24) => { 54 | $e.add_frames_i32(&*$samples).expect("Failed to analyze samples"); 55 | }; 56 | } 57 | 58 | macro_rules! test_global_loudness( 59 | ($file_name:expr, $rate:expr, $bpp:tt, $channel_map:expr, $expected_loudness:expr) => { 60 | { 61 | let (mut e, samples) = prepare_file!($file_name, $rate, $bpp, ebur128::Mode::I, $channel_map); 62 | 63 | add_samples!(e, samples, $bpp); 64 | 65 | float_eq::assert_float_eq!(e.loudness_global().expect("Failed to get global loudness"), $expected_loudness, abs <= 0.1); 66 | } 67 | 68 | { 69 | let (mut e, samples) = prepare_file!($file_name, $rate, $bpp, ebur128::Mode::I | ebur128::Mode::HISTOGRAM, $channel_map); 70 | 71 | add_samples!(e, samples, $bpp); 72 | 73 | float_eq::assert_float_eq!(e.loudness_global().expect("Failed to get global loudness"), $expected_loudness, abs <= 0.1); 74 | } 75 | }; 76 | ); 77 | 78 | macro_rules! test_true_peak( 79 | ($file_name:expr, $rate:expr, $bpp:tt, $channel_map:expr, $expected_true_peak:expr) => { 80 | let (mut e, samples) = prepare_file!($file_name, $rate, $bpp, ebur128::Mode::TRUE_PEAK, $channel_map); 81 | 82 | add_samples!(e, samples, $bpp); 83 | 84 | let mut max_true_peak = f64::MIN; 85 | for peak in (0..$channel_map.len()) 86 | .map(|c| e.true_peak(c as u32).expect("Failed to get true peak")) { 87 | if peak > max_true_peak { 88 | max_true_peak = peak; 89 | } 90 | } 91 | let max_true_peak = 20.0 * f64::log10(max_true_peak); 92 | 93 | assert!( 94 | ($expected_true_peak - 0.4..=$expected_true_peak + 0.2).contains(&max_true_peak), 95 | "{} != {}", 96 | max_true_peak, 97 | $expected_true_peak, 98 | ); 99 | }; 100 | ); 101 | 102 | macro_rules! test_loudness_range( 103 | ($file_name:expr, $rate:expr, $bpp:tt, $channel_map:expr, $expected_loudness_range:expr) => { 104 | { 105 | let (mut e, samples) = prepare_file!($file_name, $rate, $bpp, ebur128::Mode::LRA, $channel_map); 106 | 107 | add_samples!(e, samples, $bpp); 108 | 109 | let loudness_range = e.loudness_range().expect("Failed to get loudness range"); 110 | float_eq::assert_float_eq!(loudness_range, $expected_loudness_range, abs <= 1.0, "queue mode"); 111 | } 112 | 113 | { 114 | let (mut e, samples) = prepare_file!($file_name, $rate, $bpp, ebur128::Mode::LRA | ebur128::Mode::HISTOGRAM, $channel_map); 115 | 116 | add_samples!(e, samples, $bpp); 117 | 118 | let loudness_range = e.loudness_range().expect("Failed to get loudness range in histogram mode"); 119 | float_eq::assert_float_eq!(loudness_range, $expected_loudness_range, abs <= 1.0, "histogram mode"); 120 | } 121 | }; 122 | ); 123 | 124 | #[test] 125 | fn seq_3341_1() { 126 | { 127 | test_global_loudness!( 128 | "seq-3341-1-16bit.wav", 129 | 48_000, 130 | 16, 131 | [ebur128::Channel::Left, ebur128::Channel::Right], 132 | -23.0 133 | ); 134 | } 135 | 136 | { 137 | let (mut e, samples) = prepare_file!( 138 | "seq-3341-1-16bit.wav", 139 | 48_000, 140 | 16, 141 | ebur128::Mode::S, 142 | [ebur128::Channel::Left, ebur128::Channel::Right] 143 | ); 144 | 145 | // 100ms chunks / 10Hz 146 | for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { 147 | e.add_frames_i16(chunk).expect("Failed to analyze samples"); 148 | 149 | // Constant after 3s 150 | if i >= 29 { 151 | float_eq::assert_float_eq!( 152 | e.loudness_shortterm() 153 | .expect("Failed to get shortterm loudness"), 154 | -23.0, 155 | abs <= 0.1 156 | ); 157 | } 158 | } 159 | } 160 | 161 | { 162 | let (mut e, samples) = prepare_file!( 163 | "seq-3341-1-16bit.wav", 164 | 48_000, 165 | 16, 166 | ebur128::Mode::M, 167 | [ebur128::Channel::Left, ebur128::Channel::Right] 168 | ); 169 | 170 | // 100ms chunks / 10Hz 171 | for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { 172 | e.add_frames_i16(chunk).expect("Failed to analyze samples"); 173 | 174 | // Constant after 1s 175 | if i >= 10 { 176 | float_eq::assert_float_eq!( 177 | e.loudness_momentary() 178 | .expect("Failed to get momentary loudness"), 179 | -23.0, 180 | abs <= 0.1 181 | ); 182 | } 183 | } 184 | } 185 | } 186 | 187 | #[test] 188 | fn seq_3341_2() { 189 | test_global_loudness!( 190 | "seq-3341-2-16bit.wav", 191 | 48_000, 192 | 16, 193 | [ebur128::Channel::Left, ebur128::Channel::Right], 194 | -33.0 195 | ); 196 | 197 | { 198 | let (mut e, samples) = prepare_file!( 199 | "seq-3341-2-16bit.wav", 200 | 48_000, 201 | 16, 202 | ebur128::Mode::S, 203 | [ebur128::Channel::Left, ebur128::Channel::Right] 204 | ); 205 | 206 | // 100ms chunks / 10Hz 207 | for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { 208 | e.add_frames_i16(chunk).expect("Failed to analyze samples"); 209 | 210 | // Constant after 3s 211 | if i >= 29 { 212 | float_eq::assert_float_eq!( 213 | e.loudness_shortterm() 214 | .expect("Failed to get shortterm loudness"), 215 | -33.0, 216 | abs <= 0.1 217 | ); 218 | } 219 | } 220 | } 221 | 222 | { 223 | let (mut e, samples) = prepare_file!( 224 | "seq-3341-2-16bit.wav", 225 | 48_000, 226 | 16, 227 | ebur128::Mode::M, 228 | [ebur128::Channel::Left, ebur128::Channel::Right] 229 | ); 230 | 231 | // 100ms chunks / 10Hz 232 | for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { 233 | e.add_frames_i16(chunk).expect("Failed to analyze samples"); 234 | 235 | // Constant after 1s 236 | if i >= 10 { 237 | float_eq::assert_float_eq!( 238 | e.loudness_momentary() 239 | .expect("Failed to get momentary loudness"), 240 | -33.0, 241 | abs <= 0.1 242 | ); 243 | } 244 | } 245 | } 246 | } 247 | 248 | #[test] 249 | fn seq_3341_3() { 250 | test_global_loudness!( 251 | "seq-3341-3-16bit-v02.wav", 252 | 48_000, 253 | 16, 254 | [ebur128::Channel::Left, ebur128::Channel::Right], 255 | -23.0 256 | ); 257 | } 258 | 259 | #[test] 260 | fn seq_3341_4() { 261 | test_global_loudness!( 262 | "seq-3341-4-16bit-v02.wav", 263 | 48_000, 264 | 16, 265 | [ebur128::Channel::Left, ebur128::Channel::Right], 266 | -23.0 267 | ); 268 | } 269 | 270 | #[test] 271 | fn seq_3341_5() { 272 | test_global_loudness!( 273 | "seq-3341-5-16bit-v02.wav", 274 | 48_000, 275 | 16, 276 | [ebur128::Channel::Left, ebur128::Channel::Right], 277 | -23.0 278 | ); 279 | } 280 | 281 | #[test] 282 | fn seq_3341_6() { 283 | test_global_loudness!( 284 | "seq-3341-6-5channels-16bit.wav", 285 | 48_000, 286 | 16, 287 | [ 288 | ebur128::Channel::Left, 289 | ebur128::Channel::Right, 290 | ebur128::Channel::Center, 291 | ebur128::Channel::LeftSurround, 292 | ebur128::Channel::RightSurround 293 | ], 294 | -23.0 295 | ); 296 | } 297 | 298 | #[test] 299 | fn seq_3341_6_1() { 300 | test_global_loudness!( 301 | "seq-3341-6-6channels-WAVEEX-16bit.wav", 302 | 48_000, 303 | 16, 304 | [ 305 | ebur128::Channel::Left, 306 | ebur128::Channel::Right, 307 | ebur128::Channel::Center, 308 | ebur128::Channel::Unused, 309 | ebur128::Channel::LeftSurround, 310 | ebur128::Channel::RightSurround 311 | ], 312 | -23.0 313 | ); 314 | } 315 | 316 | #[test] 317 | fn seq_3341_7() { 318 | test_global_loudness!( 319 | "seq-3341-7_seq-3342-5-24bit.wav", 320 | 48_000, 321 | 24, 322 | [ebur128::Channel::Left, ebur128::Channel::Right], 323 | -23.0 324 | ); 325 | } 326 | 327 | #[test] 328 | fn seq_3341_8() { 329 | test_global_loudness!( 330 | "seq-3341-2011-8_seq-3342-6-24bit-v02.wav", 331 | 48_000, 332 | 24, 333 | [ebur128::Channel::Left, ebur128::Channel::Right], 334 | -23.0 335 | ); 336 | } 337 | 338 | #[test] 339 | fn seq_3341_9() { 340 | let (mut e, samples) = prepare_file!( 341 | "seq-3341-9-24bit.wav", 342 | 48_000, 343 | 24, 344 | ebur128::Mode::S, 345 | [ebur128::Channel::Left, ebur128::Channel::Right] 346 | ); 347 | 348 | // 100ms chunks / 10Hz 349 | for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { 350 | e.add_frames_i32(chunk).expect("Failed to analyze samples"); 351 | 352 | // Constant after 3s 353 | if i >= 29 { 354 | float_eq::assert_float_eq!( 355 | e.loudness_shortterm() 356 | .expect("Failed to get shortterm loudness"), 357 | -23.0, 358 | abs <= 0.1 359 | ); 360 | } 361 | } 362 | } 363 | 364 | #[test] 365 | fn seq_3341_10() { 366 | for i in 1..=20 { 367 | let (mut e, samples) = prepare_file!( 368 | format!("seq-3341-10-{i}-24bit.wav"), 369 | 48_000, 370 | 24, 371 | ebur128::Mode::S, 372 | [ebur128::Channel::Left, ebur128::Channel::Right] 373 | ); 374 | 375 | // 100ms chunks / 10Hz 376 | let mut max_loudness = f64::MIN; 377 | for chunk in samples.chunks(2 * 48_000 / 10) { 378 | e.add_frames_i32(chunk).expect("Failed to analyze samples"); 379 | 380 | let loudness = e 381 | .loudness_shortterm() 382 | .expect("Failed to get shortterm loudness"); 383 | if loudness > max_loudness { 384 | max_loudness = loudness; 385 | } 386 | } 387 | 388 | float_eq::assert_float_eq!(max_loudness, -23.0, abs <= 0.1, "file {}", i); 389 | } 390 | } 391 | 392 | #[test] 393 | fn seq_3341_11() { 394 | let (mut e, samples) = prepare_file!( 395 | "seq-3341-11-24bit.wav", 396 | 48_000, 397 | 24, 398 | ebur128::Mode::S, 399 | [ebur128::Channel::Left, ebur128::Channel::Right] 400 | ); 401 | 402 | // 100ms chunks / 10Hz 403 | let mut max_loudness = f64::MIN; 404 | for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { 405 | e.add_frames_i32(chunk).expect("Failed to analyze samples"); 406 | 407 | let loudness = e 408 | .loudness_shortterm() 409 | .expect("Failed to get shortterm loudness"); 410 | 411 | if loudness > max_loudness { 412 | max_loudness = loudness; 413 | } 414 | if (i + 1) % 60 == 0 { 415 | let expected = -38.0 + ((i + 1) / 60 - 1) as f64; 416 | float_eq::assert_float_eq!( 417 | max_loudness, 418 | expected, 419 | abs <= 0.1, 420 | "chunk {}", 421 | (i + 1) / 60 - 1 422 | ); 423 | } 424 | } 425 | } 426 | 427 | #[test] 428 | fn seq_3341_12() { 429 | let (mut e, samples) = prepare_file!( 430 | "seq-3341-12-24bit.wav", 431 | 48_000, 432 | 24, 433 | ebur128::Mode::M, 434 | [ebur128::Channel::Left, ebur128::Channel::Right] 435 | ); 436 | 437 | // 100ms chunks / 10Hz 438 | for (i, chunk) in samples.chunks(2 * 48_000 / 10).enumerate() { 439 | e.add_frames_i32(chunk).expect("Failed to analyze samples"); 440 | 441 | // Constant after 1s 442 | if i >= 10 { 443 | float_eq::assert_float_eq!( 444 | e.loudness_momentary() 445 | .expect("Failed to get momentary loudness"), 446 | -23.0, 447 | abs <= 0.1 448 | ); 449 | } 450 | } 451 | } 452 | 453 | #[test] 454 | fn seq_3341_13() { 455 | for i in 1..=20 { 456 | let (mut e, samples) = prepare_file!( 457 | format!( 458 | "seq-3341-13-{}-24bit.wav{}", 459 | i, 460 | if i > 2 { ".wav" } else { "" } 461 | ), 462 | 48_000, 463 | 24, 464 | ebur128::Mode::M, 465 | [ebur128::Channel::Left, ebur128::Channel::Right] 466 | ); 467 | 468 | // 10ms chunks / 100Hz 469 | let mut max_loudness = f64::MIN; 470 | for chunk in samples.chunks(2 * 48_000 / 100) { 471 | e.add_frames_i32(chunk).expect("Failed to analyze samples"); 472 | 473 | let loudness = e 474 | .loudness_momentary() 475 | .expect("Failed to get momentary loudness"); 476 | if loudness > max_loudness { 477 | max_loudness = loudness; 478 | } 479 | } 480 | 481 | float_eq::assert_float_eq!(max_loudness, -23.0, abs <= 0.1, "file {}", i); 482 | } 483 | } 484 | 485 | #[test] 486 | fn seq_3341_14() { 487 | let (mut e, samples) = prepare_file!( 488 | "seq-3341-14-24bit.wav.wav", 489 | 48_000, 490 | 24, 491 | ebur128::Mode::M, 492 | [ebur128::Channel::Left, ebur128::Channel::Right] 493 | ); 494 | 495 | // 10ms chunks / 100Hz 496 | let mut max_loudness = f64::MIN; 497 | for (i, chunk) in samples.chunks(2 * 48_000 / 100).enumerate() { 498 | e.add_frames_i32(chunk).expect("Failed to analyze samples"); 499 | 500 | let loudness = e 501 | .loudness_momentary() 502 | .expect("Failed to get momentary loudness"); 503 | 504 | if loudness > max_loudness { 505 | max_loudness = loudness; 506 | } 507 | if (i + 1) % 80 == 0 { 508 | let expected = -38.0 + ((i + 1) / 80 - 1) as f64; 509 | float_eq::assert_float_eq!( 510 | max_loudness, 511 | expected, 512 | abs <= 0.1, 513 | "chunk {}", 514 | (i + 1) / 80 - 1 515 | ); 516 | } 517 | } 518 | } 519 | 520 | #[test] 521 | fn seq_3341_15() { 522 | test_true_peak!( 523 | "seq-3341-15-24bit.wav.wav", 524 | 48_000, 525 | 24, 526 | [ebur128::Channel::Left, ebur128::Channel::Right], 527 | -6.0 528 | ); 529 | } 530 | 531 | #[test] 532 | fn seq_3341_16() { 533 | test_true_peak!( 534 | "seq-3341-16-24bit.wav.wav", 535 | 48_000, 536 | 24, 537 | [ebur128::Channel::Left, ebur128::Channel::Right], 538 | -6.0 539 | ); 540 | } 541 | 542 | #[test] 543 | fn seq_3341_17() { 544 | test_true_peak!( 545 | "seq-3341-17-24bit.wav.wav", 546 | 48_000, 547 | 24, 548 | [ebur128::Channel::Left, ebur128::Channel::Right], 549 | -6.0 550 | ); 551 | } 552 | 553 | #[test] 554 | fn seq_3341_18() { 555 | test_true_peak!( 556 | "seq-3341-18-24bit.wav.wav", 557 | 48_000, 558 | 24, 559 | [ebur128::Channel::Left, ebur128::Channel::Right], 560 | -6.0 561 | ); 562 | } 563 | 564 | #[test] 565 | fn seq_3341_19() { 566 | test_true_peak!( 567 | "seq-3341-19-24bit.wav.wav", 568 | 48_000, 569 | 24, 570 | [ebur128::Channel::Left, ebur128::Channel::Right], 571 | 3.0 572 | ); 573 | } 574 | 575 | #[test] 576 | fn seq_3341_20() { 577 | test_true_peak!( 578 | "seq-3341-20-24bit.wav.wav", 579 | 48_000, 580 | 24, 581 | [ebur128::Channel::Left, ebur128::Channel::Right], 582 | 0.0 583 | ); 584 | } 585 | 586 | #[test] 587 | fn seq_3341_21() { 588 | test_true_peak!( 589 | "seq-3341-21-24bit.wav.wav", 590 | 48_000, 591 | 24, 592 | [ebur128::Channel::Left, ebur128::Channel::Right], 593 | 0.0 594 | ); 595 | } 596 | 597 | #[test] 598 | fn seq_3341_22() { 599 | test_true_peak!( 600 | "seq-3341-22-24bit.wav.wav", 601 | 48_000, 602 | 24, 603 | [ebur128::Channel::Left, ebur128::Channel::Right], 604 | 0.0 605 | ); 606 | } 607 | 608 | #[test] 609 | fn seq_3341_23() { 610 | test_true_peak!( 611 | "seq-3341-23-24bit.wav.wav", 612 | 48_000, 613 | 24, 614 | [ebur128::Channel::Left, ebur128::Channel::Right], 615 | 0.0 616 | ); 617 | } 618 | 619 | #[test] 620 | fn seq_3342_1() { 621 | test_loudness_range!( 622 | "seq-3342-1-16bit.wav", 623 | 48_000, 624 | 16, 625 | [ebur128::Channel::Left, ebur128::Channel::Right], 626 | 10.0 627 | ); 628 | } 629 | 630 | #[test] 631 | fn seq_3342_2() { 632 | test_loudness_range!( 633 | "seq-3342-2-16bit.wav", 634 | 48_000, 635 | 16, 636 | [ebur128::Channel::Left, ebur128::Channel::Right], 637 | 5.0 638 | ); 639 | } 640 | 641 | #[test] 642 | fn seq_3342_3() { 643 | test_loudness_range!( 644 | "seq-3342-3-16bit.wav", 645 | 48_000, 646 | 16, 647 | [ebur128::Channel::Left, ebur128::Channel::Right], 648 | 20.0 649 | ); 650 | } 651 | 652 | #[test] 653 | fn seq_3342_4() { 654 | test_loudness_range!( 655 | "seq-3342-4-16bit.wav", 656 | 48_000, 657 | 16, 658 | [ebur128::Channel::Left, ebur128::Channel::Right], 659 | 15.0 660 | ); 661 | } 662 | 663 | #[test] 664 | fn seq_3342_5() { 665 | test_loudness_range!( 666 | "seq-3341-7_seq-3342-5-24bit.wav", 667 | 48_000, 668 | 24, 669 | [ebur128::Channel::Left, ebur128::Channel::Right], 670 | 5.0 671 | ); 672 | } 673 | 674 | #[test] 675 | fn seq_3342_6() { 676 | test_loudness_range!( 677 | "seq-3341-2011-8_seq-3342-6-24bit-v02.wav", 678 | 48_000, 679 | 24, 680 | [ebur128::Channel::Left, ebur128::Channel::Right], 681 | 15.0 682 | ); 683 | } 684 | -------------------------------------------------------------------------------- /tests/c/queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1991, 1993 3 | * The Regents of the University of California. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. Neither the name of the University nor the names of its contributors 14 | * may be used to endorse or promote products derived from this software 15 | * without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | * SUCH DAMAGE. 28 | * 29 | * @(#)queue.h 8.5 (Berkeley) 8/20/94 30 | */ 31 | 32 | #ifndef _SYS_QUEUE_H_ 33 | #define _SYS_QUEUE_H_ 34 | 35 | /* 36 | * This file defines five types of data structures: singly-linked lists, 37 | * lists, simple queues, tail queues, and circular queues. 38 | * 39 | * A singly-linked list is headed by a single forward pointer. The 40 | * elements are singly linked for minimum space and pointer manipulation 41 | * overhead at the expense of O(n) removal for arbitrary elements. New 42 | * elements can be added to the list after an existing element or at the 43 | * head of the list. Elements being removed from the head of the list 44 | * should use the explicit macro for this purpose for optimum 45 | * efficiency. A singly-linked list may only be traversed in the forward 46 | * direction. Singly-linked lists are ideal for applications with large 47 | * datasets and few or no removals or for implementing a LIFO queue. 48 | * 49 | * A list is headed by a single forward pointer (or an array of forward 50 | * pointers for a hash table header). The elements are doubly linked 51 | * so that an arbitrary element can be removed without a need to 52 | * traverse the list. New elements can be added to the list before 53 | * or after an existing element or at the head of the list. A list 54 | * may only be traversed in the forward direction. 55 | * 56 | * A simple queue is headed by a pair of pointers, one the head of the 57 | * list and the other to the tail of the list. The elements are singly 58 | * linked to save space, so elements can only be removed from the 59 | * head of the list. New elements can be added to the list after 60 | * an existing element, at the head of the list, or at the end of the 61 | * list. A simple queue may only be traversed in the forward direction. 62 | * 63 | * A tail queue is headed by a pair of pointers, one to the head of the 64 | * list and the other to the tail of the list. The elements are doubly 65 | * linked so that an arbitrary element can be removed without a need to 66 | * traverse the list. New elements can be added to the list before or 67 | * after an existing element, at the head of the list, or at the end of 68 | * the list. A tail queue may be traversed in either direction. 69 | * 70 | * A circle queue is headed by a pair of pointers, one to the head of the 71 | * list and the other to the tail of the list. The elements are doubly 72 | * linked so that an arbitrary element can be removed without a need to 73 | * traverse the list. New elements can be added to the list before or after 74 | * an existing element, at the head of the list, or at the end of the list. 75 | * A circle queue may be traversed in either direction, but has a more 76 | * complex end of list detection. 77 | * 78 | * For details on the use of these macros, see the queue(3) manual page. 79 | */ 80 | 81 | /* 82 | * List definitions. 83 | */ 84 | #define LIST_HEAD(name, type) \ 85 | struct name { \ 86 | struct type *lh_first; /* first element */ \ 87 | } 88 | 89 | #define LIST_HEAD_INITIALIZER(head) \ 90 | { NULL } 91 | 92 | #define LIST_ENTRY(type) \ 93 | struct { \ 94 | struct type *le_next; /* next element */ \ 95 | struct type **le_prev; /* address of previous next element */ \ 96 | } 97 | 98 | /* 99 | * List functions. 100 | */ 101 | #define LIST_INIT(head) do { \ 102 | (head)->lh_first = NULL; \ 103 | } while (/*CONSTCOND*/0) 104 | 105 | #define LIST_INSERT_AFTER(listelm, elm, field) do { \ 106 | if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ 107 | (listelm)->field.le_next->field.le_prev = \ 108 | &(elm)->field.le_next; \ 109 | (listelm)->field.le_next = (elm); \ 110 | (elm)->field.le_prev = &(listelm)->field.le_next; \ 111 | } while (/*CONSTCOND*/0) 112 | 113 | #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ 114 | (elm)->field.le_prev = (listelm)->field.le_prev; \ 115 | (elm)->field.le_next = (listelm); \ 116 | *(listelm)->field.le_prev = (elm); \ 117 | (listelm)->field.le_prev = &(elm)->field.le_next; \ 118 | } while (/*CONSTCOND*/0) 119 | 120 | #define LIST_INSERT_HEAD(head, elm, field) do { \ 121 | if (((elm)->field.le_next = (head)->lh_first) != NULL) \ 122 | (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ 123 | (head)->lh_first = (elm); \ 124 | (elm)->field.le_prev = &(head)->lh_first; \ 125 | } while (/*CONSTCOND*/0) 126 | 127 | #define LIST_REMOVE(elm, field) do { \ 128 | if ((elm)->field.le_next != NULL) \ 129 | (elm)->field.le_next->field.le_prev = \ 130 | (elm)->field.le_prev; \ 131 | *(elm)->field.le_prev = (elm)->field.le_next; \ 132 | } while (/*CONSTCOND*/0) 133 | 134 | #define LIST_FOREACH(var, head, field) \ 135 | for ((var) = ((head)->lh_first); \ 136 | (var); \ 137 | (var) = ((var)->field.le_next)) 138 | 139 | /* 140 | * List access methods. 141 | */ 142 | #define LIST_EMPTY(head) ((head)->lh_first == NULL) 143 | #define LIST_FIRST(head) ((head)->lh_first) 144 | #define LIST_NEXT(elm, field) ((elm)->field.le_next) 145 | 146 | 147 | /* 148 | * Singly-linked List definitions. 149 | */ 150 | #define SLIST_HEAD(name, type) \ 151 | struct name { \ 152 | struct type *slh_first; /* first element */ \ 153 | } 154 | 155 | #define SLIST_HEAD_INITIALIZER(head) \ 156 | { NULL } 157 | 158 | #define SLIST_ENTRY(type) \ 159 | struct { \ 160 | struct type *sle_next; /* next element */ \ 161 | } 162 | 163 | /* 164 | * Singly-linked List functions. 165 | */ 166 | #define SLIST_INIT(head) do { \ 167 | (head)->slh_first = NULL; \ 168 | } while (/*CONSTCOND*/0) 169 | 170 | #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ 171 | (elm)->field.sle_next = (slistelm)->field.sle_next; \ 172 | (slistelm)->field.sle_next = (elm); \ 173 | } while (/*CONSTCOND*/0) 174 | 175 | #define SLIST_INSERT_HEAD(head, elm, field) do { \ 176 | (elm)->field.sle_next = (head)->slh_first; \ 177 | (head)->slh_first = (elm); \ 178 | } while (/*CONSTCOND*/0) 179 | 180 | #define SLIST_REMOVE_HEAD(head, field) do { \ 181 | (head)->slh_first = (head)->slh_first->field.sle_next; \ 182 | } while (/*CONSTCOND*/0) 183 | 184 | #define SLIST_REMOVE(head, elm, type, field) do { \ 185 | if ((head)->slh_first == (elm)) { \ 186 | SLIST_REMOVE_HEAD((head), field); \ 187 | } \ 188 | else { \ 189 | struct type *curelm = (head)->slh_first; \ 190 | while(curelm->field.sle_next != (elm)) \ 191 | curelm = curelm->field.sle_next; \ 192 | curelm->field.sle_next = \ 193 | curelm->field.sle_next->field.sle_next; \ 194 | } \ 195 | } while (/*CONSTCOND*/0) 196 | 197 | #define SLIST_FOREACH(var, head, field) \ 198 | for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next) 199 | 200 | /* 201 | * Singly-linked List access methods. 202 | */ 203 | #define SLIST_EMPTY(head) ((head)->slh_first == NULL) 204 | #define SLIST_FIRST(head) ((head)->slh_first) 205 | #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) 206 | 207 | 208 | /* 209 | * Singly-linked Tail queue declarations. 210 | */ 211 | #define STAILQ_HEAD(name, type) \ 212 | struct name { \ 213 | struct type *stqh_first; /* first element */ \ 214 | struct type **stqh_last; /* addr of last next element */ \ 215 | } 216 | 217 | #define STAILQ_HEAD_INITIALIZER(head) \ 218 | { NULL, &(head).stqh_first } 219 | 220 | #define STAILQ_ENTRY(type) \ 221 | struct { \ 222 | struct type *stqe_next; /* next element */ \ 223 | } 224 | 225 | /* 226 | * Singly-linked Tail queue functions. 227 | */ 228 | #define STAILQ_INIT(head) do { \ 229 | (head)->stqh_first = NULL; \ 230 | (head)->stqh_last = &(head)->stqh_first; \ 231 | } while (/*CONSTCOND*/0) 232 | 233 | #define STAILQ_INSERT_HEAD(head, elm, field) do { \ 234 | if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ 235 | (head)->stqh_last = &(elm)->field.stqe_next; \ 236 | (head)->stqh_first = (elm); \ 237 | } while (/*CONSTCOND*/0) 238 | 239 | #define STAILQ_INSERT_TAIL(head, elm, field) do { \ 240 | (elm)->field.stqe_next = NULL; \ 241 | *(head)->stqh_last = (elm); \ 242 | (head)->stqh_last = &(elm)->field.stqe_next; \ 243 | } while (/*CONSTCOND*/0) 244 | 245 | #define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ 246 | if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\ 247 | (head)->stqh_last = &(elm)->field.stqe_next; \ 248 | (listelm)->field.stqe_next = (elm); \ 249 | } while (/*CONSTCOND*/0) 250 | 251 | #define STAILQ_REMOVE_HEAD(head, field) do { \ 252 | if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \ 253 | (head)->stqh_last = &(head)->stqh_first; \ 254 | } while (/*CONSTCOND*/0) 255 | 256 | #define STAILQ_REMOVE(head, elm, type, field) do { \ 257 | if ((head)->stqh_first == (elm)) { \ 258 | STAILQ_REMOVE_HEAD((head), field); \ 259 | } else { \ 260 | struct type *curelm = (head)->stqh_first; \ 261 | while (curelm->field.stqe_next != (elm)) \ 262 | curelm = curelm->field.stqe_next; \ 263 | if ((curelm->field.stqe_next = \ 264 | curelm->field.stqe_next->field.stqe_next) == NULL) \ 265 | (head)->stqh_last = &(curelm)->field.stqe_next; \ 266 | } \ 267 | } while (/*CONSTCOND*/0) 268 | 269 | #define STAILQ_FOREACH(var, head, field) \ 270 | for ((var) = ((head)->stqh_first); \ 271 | (var); \ 272 | (var) = ((var)->field.stqe_next)) 273 | 274 | #define STAILQ_CONCAT(head1, head2) do { \ 275 | if (!STAILQ_EMPTY((head2))) { \ 276 | *(head1)->stqh_last = (head2)->stqh_first; \ 277 | (head1)->stqh_last = (head2)->stqh_last; \ 278 | STAILQ_INIT((head2)); \ 279 | } \ 280 | } while (/*CONSTCOND*/0) 281 | 282 | /* 283 | * Singly-linked Tail queue access methods. 284 | */ 285 | #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) 286 | #define STAILQ_FIRST(head) ((head)->stqh_first) 287 | #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) 288 | 289 | 290 | /* 291 | * Simple queue definitions. 292 | */ 293 | #define SIMPLEQ_HEAD(name, type) \ 294 | struct name { \ 295 | struct type *sqh_first; /* first element */ \ 296 | struct type **sqh_last; /* addr of last next element */ \ 297 | } 298 | 299 | #define SIMPLEQ_HEAD_INITIALIZER(head) \ 300 | { NULL, &(head).sqh_first } 301 | 302 | #define SIMPLEQ_ENTRY(type) \ 303 | struct { \ 304 | struct type *sqe_next; /* next element */ \ 305 | } 306 | 307 | /* 308 | * Simple queue functions. 309 | */ 310 | #define SIMPLEQ_INIT(head) do { \ 311 | (head)->sqh_first = NULL; \ 312 | (head)->sqh_last = &(head)->sqh_first; \ 313 | } while (/*CONSTCOND*/0) 314 | 315 | #define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ 316 | if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ 317 | (head)->sqh_last = &(elm)->field.sqe_next; \ 318 | (head)->sqh_first = (elm); \ 319 | } while (/*CONSTCOND*/0) 320 | 321 | #define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ 322 | (elm)->field.sqe_next = NULL; \ 323 | *(head)->sqh_last = (elm); \ 324 | (head)->sqh_last = &(elm)->field.sqe_next; \ 325 | } while (/*CONSTCOND*/0) 326 | 327 | #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ 328 | if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ 329 | (head)->sqh_last = &(elm)->field.sqe_next; \ 330 | (listelm)->field.sqe_next = (elm); \ 331 | } while (/*CONSTCOND*/0) 332 | 333 | #define SIMPLEQ_REMOVE_HEAD(head, field) do { \ 334 | if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ 335 | (head)->sqh_last = &(head)->sqh_first; \ 336 | } while (/*CONSTCOND*/0) 337 | 338 | #define SIMPLEQ_REMOVE(head, elm, type, field) do { \ 339 | if ((head)->sqh_first == (elm)) { \ 340 | SIMPLEQ_REMOVE_HEAD((head), field); \ 341 | } else { \ 342 | struct type *curelm = (head)->sqh_first; \ 343 | while (curelm->field.sqe_next != (elm)) \ 344 | curelm = curelm->field.sqe_next; \ 345 | if ((curelm->field.sqe_next = \ 346 | curelm->field.sqe_next->field.sqe_next) == NULL) \ 347 | (head)->sqh_last = &(curelm)->field.sqe_next; \ 348 | } \ 349 | } while (/*CONSTCOND*/0) 350 | 351 | #define SIMPLEQ_FOREACH(var, head, field) \ 352 | for ((var) = ((head)->sqh_first); \ 353 | (var); \ 354 | (var) = ((var)->field.sqe_next)) 355 | 356 | /* 357 | * Simple queue access methods. 358 | */ 359 | #define SIMPLEQ_EMPTY(head) ((head)->sqh_first == NULL) 360 | #define SIMPLEQ_FIRST(head) ((head)->sqh_first) 361 | #define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) 362 | 363 | 364 | /* 365 | * Tail queue definitions. 366 | */ 367 | #define _TAILQ_HEAD(name, type, qual) \ 368 | struct name { \ 369 | qual type *tqh_first; /* first element */ \ 370 | qual type *qual *tqh_last; /* addr of last next element */ \ 371 | } 372 | #define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) 373 | 374 | #define TAILQ_HEAD_INITIALIZER(head) \ 375 | { NULL, &(head).tqh_first } 376 | 377 | #define _TAILQ_ENTRY(type, qual) \ 378 | struct { \ 379 | qual type *tqe_next; /* next element */ \ 380 | qual type *qual *tqe_prev; /* address of previous next element */\ 381 | } 382 | #define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) 383 | 384 | /* 385 | * Tail queue functions. 386 | */ 387 | #define TAILQ_INIT(head) do { \ 388 | (head)->tqh_first = NULL; \ 389 | (head)->tqh_last = &(head)->tqh_first; \ 390 | } while (/*CONSTCOND*/0) 391 | 392 | #define TAILQ_INSERT_HEAD(head, elm, field) do { \ 393 | if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ 394 | (head)->tqh_first->field.tqe_prev = \ 395 | &(elm)->field.tqe_next; \ 396 | else \ 397 | (head)->tqh_last = &(elm)->field.tqe_next; \ 398 | (head)->tqh_first = (elm); \ 399 | (elm)->field.tqe_prev = &(head)->tqh_first; \ 400 | } while (/*CONSTCOND*/0) 401 | 402 | #define TAILQ_INSERT_TAIL(head, elm, field) do { \ 403 | (elm)->field.tqe_next = NULL; \ 404 | (elm)->field.tqe_prev = (head)->tqh_last; \ 405 | *(head)->tqh_last = (elm); \ 406 | (head)->tqh_last = &(elm)->field.tqe_next; \ 407 | } while (/*CONSTCOND*/0) 408 | 409 | #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ 410 | if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ 411 | (elm)->field.tqe_next->field.tqe_prev = \ 412 | &(elm)->field.tqe_next; \ 413 | else \ 414 | (head)->tqh_last = &(elm)->field.tqe_next; \ 415 | (listelm)->field.tqe_next = (elm); \ 416 | (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ 417 | } while (/*CONSTCOND*/0) 418 | 419 | #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ 420 | (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ 421 | (elm)->field.tqe_next = (listelm); \ 422 | *(listelm)->field.tqe_prev = (elm); \ 423 | (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ 424 | } while (/*CONSTCOND*/0) 425 | 426 | #define TAILQ_REMOVE(head, elm, field) do { \ 427 | if (((elm)->field.tqe_next) != NULL) \ 428 | (elm)->field.tqe_next->field.tqe_prev = \ 429 | (elm)->field.tqe_prev; \ 430 | else \ 431 | (head)->tqh_last = (elm)->field.tqe_prev; \ 432 | *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ 433 | } while (/*CONSTCOND*/0) 434 | 435 | #define TAILQ_FOREACH(var, head, field) \ 436 | for ((var) = ((head)->tqh_first); \ 437 | (var); \ 438 | (var) = ((var)->field.tqe_next)) 439 | 440 | #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ 441 | for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last)); \ 442 | (var); \ 443 | (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) 444 | 445 | #define TAILQ_CONCAT(head1, head2, field) do { \ 446 | if (!TAILQ_EMPTY(head2)) { \ 447 | *(head1)->tqh_last = (head2)->tqh_first; \ 448 | (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ 449 | (head1)->tqh_last = (head2)->tqh_last; \ 450 | TAILQ_INIT((head2)); \ 451 | } \ 452 | } while (/*CONSTCOND*/0) 453 | 454 | /* 455 | * Tail queue access methods. 456 | */ 457 | #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) 458 | #define TAILQ_FIRST(head) ((head)->tqh_first) 459 | #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) 460 | 461 | #define TAILQ_LAST(head, headname) \ 462 | (*(((struct headname *)((head)->tqh_last))->tqh_last)) 463 | #define TAILQ_PREV(elm, headname, field) \ 464 | (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) 465 | 466 | 467 | /* 468 | * Circular queue definitions. 469 | */ 470 | #define CIRCLEQ_HEAD(name, type) \ 471 | struct name { \ 472 | struct type *cqh_first; /* first element */ \ 473 | struct type *cqh_last; /* last element */ \ 474 | } 475 | 476 | #define CIRCLEQ_HEAD_INITIALIZER(head) \ 477 | { (void *)&head, (void *)&head } 478 | 479 | #define CIRCLEQ_ENTRY(type) \ 480 | struct { \ 481 | struct type *cqe_next; /* next element */ \ 482 | struct type *cqe_prev; /* previous element */ \ 483 | } 484 | 485 | /* 486 | * Circular queue functions. 487 | */ 488 | #define CIRCLEQ_INIT(head) do { \ 489 | (head)->cqh_first = (void *)(head); \ 490 | (head)->cqh_last = (void *)(head); \ 491 | } while (/*CONSTCOND*/0) 492 | 493 | #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ 494 | (elm)->field.cqe_next = (listelm)->field.cqe_next; \ 495 | (elm)->field.cqe_prev = (listelm); \ 496 | if ((listelm)->field.cqe_next == (void *)(head)) \ 497 | (head)->cqh_last = (elm); \ 498 | else \ 499 | (listelm)->field.cqe_next->field.cqe_prev = (elm); \ 500 | (listelm)->field.cqe_next = (elm); \ 501 | } while (/*CONSTCOND*/0) 502 | 503 | #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ 504 | (elm)->field.cqe_next = (listelm); \ 505 | (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ 506 | if ((listelm)->field.cqe_prev == (void *)(head)) \ 507 | (head)->cqh_first = (elm); \ 508 | else \ 509 | (listelm)->field.cqe_prev->field.cqe_next = (elm); \ 510 | (listelm)->field.cqe_prev = (elm); \ 511 | } while (/*CONSTCOND*/0) 512 | 513 | #define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ 514 | (elm)->field.cqe_next = (head)->cqh_first; \ 515 | (elm)->field.cqe_prev = (void *)(head); \ 516 | if ((head)->cqh_last == (void *)(head)) \ 517 | (head)->cqh_last = (elm); \ 518 | else \ 519 | (head)->cqh_first->field.cqe_prev = (elm); \ 520 | (head)->cqh_first = (elm); \ 521 | } while (/*CONSTCOND*/0) 522 | 523 | #define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ 524 | (elm)->field.cqe_next = (void *)(head); \ 525 | (elm)->field.cqe_prev = (head)->cqh_last; \ 526 | if ((head)->cqh_first == (void *)(head)) \ 527 | (head)->cqh_first = (elm); \ 528 | else \ 529 | (head)->cqh_last->field.cqe_next = (elm); \ 530 | (head)->cqh_last = (elm); \ 531 | } while (/*CONSTCOND*/0) 532 | 533 | #define CIRCLEQ_REMOVE(head, elm, field) do { \ 534 | if ((elm)->field.cqe_next == (void *)(head)) \ 535 | (head)->cqh_last = (elm)->field.cqe_prev; \ 536 | else \ 537 | (elm)->field.cqe_next->field.cqe_prev = \ 538 | (elm)->field.cqe_prev; \ 539 | if ((elm)->field.cqe_prev == (void *)(head)) \ 540 | (head)->cqh_first = (elm)->field.cqe_next; \ 541 | else \ 542 | (elm)->field.cqe_prev->field.cqe_next = \ 543 | (elm)->field.cqe_next; \ 544 | } while (/*CONSTCOND*/0) 545 | 546 | #define CIRCLEQ_FOREACH(var, head, field) \ 547 | for ((var) = ((head)->cqh_first); \ 548 | (var) != (const void *)(head); \ 549 | (var) = ((var)->field.cqe_next)) 550 | 551 | #define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ 552 | for ((var) = ((head)->cqh_last); \ 553 | (var) != (const void *)(head); \ 554 | (var) = ((var)->field.cqe_prev)) 555 | 556 | /* 557 | * Circular queue access methods. 558 | */ 559 | #define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) 560 | #define CIRCLEQ_FIRST(head) ((head)->cqh_first) 561 | #define CIRCLEQ_LAST(head) ((head)->cqh_last) 562 | #define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) 563 | #define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) 564 | 565 | #define CIRCLEQ_LOOP_NEXT(head, elm, field) \ 566 | (((elm)->field.cqe_next == (void *)(head)) \ 567 | ? ((head)->cqh_first) \ 568 | : (elm->field.cqe_next)) 569 | #define CIRCLEQ_LOOP_PREV(head, elm, field) \ 570 | (((elm)->field.cqe_prev == (void *)(head)) \ 571 | ? ((head)->cqh_last) \ 572 | : (elm->field.cqe_prev)) 573 | 574 | #endif /* sys/queue.h */ 575 | --------------------------------------------------------------------------------