A crate providing the fundamentals for working with PCM (pulse-code modulation)
DSP (digital signal processing). In other words,
sample provides a suite of
low-level, high-performance tools including types, traits and functions for
working with digital audio signals.
sample crate requires no dynamic allocations1 and has no
dependencies. The goal is to design a library akin to the std, but for audio
DSP; keeping the focus on portable and fast fundamentals.
1: Besides the
Signal::bus method, which is only necessary when
Signal tree into a directed acyclic graph.
Find the API documentation here.
Use the Sample trait to convert between and remain generic over any bit-depth in an optimal, performance-sensitive manner. Implementations are provided for all signed integer, unsigned integer and floating point primitive types along with some custom types including 11, 20, 24 and 48-bit signed and unsigned unpacked integers. For example:
assert_eq!((-1.0).to_sample::<u8>(), 0); assert_eq!(0.0.to_sample::<u8>(), 128); assert_eq!(0i32.to_sample::<u32>(), 2_147_483_648); assert_eq!(I24::new(0).unwrap(), Sample::from_sample(0.0)); assert_eq!(0.0, Sample::equilibrium());
Use the Frame trait to remain generic over the number of channels at a discrete moment in time. Implementations are provided for all fixed-size arrays up to 32 elements in length.
let foo = [0.1, 0.2, -0.1, -0.2]; let bar = foo.scale_amp(2.0); assert_eq!(bar, [0.2, 0.4, -0.2, -0.4]); assert_eq!(Mono::<f32>::equilibrium(), [0.0]); assert_eq!(Stereo::<f32>::equilibrium(), [0.0, 0.0]); assert_eq!(<[f32; 3]>::equilibrium(), [0.0, 0.0, 0.0]); let foo = [0i16, 0]; let bar: [u8; 2] = foo.map(Sample::to_sample); assert_eq!(bar, [128u8, 128]);
Use the Signal trait for working with infinite-iterator-like types that
Frames. Signal provides methods for adding, scaling, offsetting,
multiplying, clipping, generating, monitoring and buffering streams of
Working with Signals allows for easy, readable creation of rich and complex
DSP graphs with a simple and familiar API.
// Clip to an amplitude of 0.9. let frames = [[1.2, 0.8], [-0.7, -1.4]]; let clipped: Vec<_> = signal::from_iter(frames.iter().cloned()).clip_amp(0.9).take(2).collect(); assert_eq!(clipped, vec![[0.9, 0.8], [-0.7, -0.9]]); // Add `a` with `b` and yield the result. let a = [[0.2], [-0.6], [0.5]]; let b = [[0.2], [0.1], [-0.8]]; let a_signal = signal::from_iter(a.iter().cloned()); let b_signal = signal::from_iter(b.iter().cloned()); let added: Vec<[f32; 1]> = a_signal.add_amp(b_signal).take(3).collect(); assert_eq!(added, vec![[0.4], [-0.5], [-0.3]]); // Scale the playback rate by `0.5`. let foo = [[0.0], [1.0], [0.0], [-1.0]]; let mut source = signal::from_iter(foo.iter().cloned()); let interp = Linear::from_source(&mut source); let frames: Vec<_> = source.scale_hz(interp, 0.5).take(8).collect(); assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); // Convert a signal to its RMS. let signal = signal::rate(44_100.0).const_hz(440.0).sine();; let ring_buffer = ring_buffer::Fixed::from([[0.0]; WINDOW_SIZE]); let mut rms_signal = signal.rms(ring_buffer);
The signal module also provides a series of Signal source types, including:
Gen(generate frames from a Fn() -> F)
GenMut(generate frames from a FnMut() -> F)
Use the slice module functions for processing chunks of
Conversion functions are provided for safely converting between slices of
Samples and slices of
Frames without requiring any allocation.
let frames = &[[0.0, 0.5], [0.0, -0.5]][..]; let samples = sample::slice::to_sample_slice(frames); assert_eq!(samples, &[0.0, 0.5, 0.0, -0.5][..]); let samples = &[0.0, 0.5, 0.0, -0.5][..]; let frames = sample::slice::to_frame_slice(samples); assert_eq!(frames, Some(&[[0.0, 0.5], [0.0, -0.5]][..])); let samples = &[0.0, 0.5, 0.0][..]; let frames = sample::slice::to_frame_slice(samples); assert_eq!(frames, None::<&[[f32; 2]]>);
The conv module provides pure functions and traits for more specific conversions. A function is provided for converting between every possible pair of sample types. Traits include:
The interpolate module provides a Converter type, for converting and interpolating the rate of Signals. This can be useful for both sample rate conversion and playback rate multiplication. Converters can use a range of interpolation methods, with Floor, Linear, and Sinc interpolation provided in the library. (NB: Sinc interpolation currently requires heap allocation, as it uses VecDeque.)
The ring_buffer module provides generic Fixed and Bounded ring buffer types, both of which may be used with owned, borrowed, stack and allocated buffers.
The peak module can be used for monitoring the peak of a signal. Provided
peak rectifiers include
The rms module provides a flexible Rms type that can be used for RMS (root mean square) detection. Any Fixed ring buffer can be used as the window for the RMS detection.
The envelope module provides a Detector type (also known as a Follower) that allows for detecting the envelope of a signal. Detector is generic over the type of Detection - Rms and Peak detection are provided. For example:
let signal = signal::rate(4.0).const_hz(1.0).sine(); let attack = 1.0; let release = 1.0; let detector = envelope::Detector::peak(attack, release); let mut envelope = signal.detect_envelope(detector); assert_eq!( envelope.take(4).collect::<Vec<_>>(), vec![[0.0], [0.6321205496788025], [0.23254416035257117], [0.7176687675647109]] );
Using in a
This crate is largely dependency free, even of things outside
no_std cargo feature will enable using
sample in these environments.
Currently, only nightly is supported, because it explicitly depends on the
collections for datastructures and
core_intrinsics for some of
the math. If this restriction is onerous for you, it can be lifted with minor
loss of functionality (the
Signal::bus method), so open an issue!
If the sample crate is missing types, conversions or other fundamental functionality that you wish it had, feel free to open an issue or pull request! The more hands on deck, the merrier :)
Licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.