heron_sound/
lib.rs

1// #![doc = include_str!("../README.md")]
2//! `heron-sound` is a library of DSP utilities used in [Heron Sounds](https://heronsounds.com) audio plugins.
3//! 
4//! Currently supports the following DSP components:
5//! 
6//! - [Envelope generators](crate::env) (ADSR, Exponential ADSR, DA)
7//! - [Envelope follower](crate::follow::EnvFollower)
8//! - [Filters](crate::filter) (State-variable)
9//! - [Basic waveforms](crate::wave) (Saw, Sine, Pulse, Triangle)
10//! - [Oscillators](crate::osc) built on basic waveforms
11//! - [Low-frequency oscillators](crate::lfo)
12//! - Tools for [parameter modulation](crate::modulate)
13//! - Tools for [pitch manipulation](crate::pitch)
14//! - [Bitmask](crate::util::Bitmask) implementation
15//! - Tools for [sample rate manipulation](crate::clock)
16//! 
17//! As well as a variety of utilities to support the above.
18//!
19//! ## Implementation
20//! 
21//! To help develop DSP components, we provide two basic traits,
22//! [Gen](crate::Gen) and [Proc](crate::Proc).
23//! `Gen` is for components that generate some value from their internal state
24//! without any explicit input,
25//! while `Proc` is for processors that take some input value and produce an output.
26//! The inputs and outputs may be samples, gains, or more complex types,
27//! but should not be user-modifiable parameters.
28//! 
29//! To pass in user-modifiable parameters,
30//! both traits have a `Spec` associated type
31//! which is passed by shared reference at processing time and evaluated,
32//! along with the processor's internal state,
33//! to produce an output.
34//! 
35//! For example, an envelope follower can be implemented as a [Proc](crate::Proc) that takes an input sample
36//! and returns a bool indicating whether the envelope is engaged or not:
37//! ```rust
38//! struct EnvFollower {
39//!     engaged: bool,
40//!     counter: u32,
41//! }
42//! 
43//! struct EnvFollowerSpec {
44//!     threshold: f64,
45//!     hold_time: u32,
46//! }
47//! 
48//! # use heron_sound::Proc;
49//! impl Proc<f64, bool> for EnvFollower {
50//!     type Spec = EnvFollowerSpec;
51//!     fn proc(&mut self, spec: &Self::Spec, sample: f64) -> bool {
52//!         // implementation omitted
53//! # return false;
54//!     }
55//! }
56//! ```
57//! 
58//! Its internal state keeps track of whether it's currently engaged and for how long,
59//! and its associated `Spec` type keeps track of the amplitude threshold and hold time,
60//! both of which can be set by the user.
61//! At processing time, it checks whether the input sample's amplitude is above the threshold,
62//! increments its internal counter and checks it against the user-set hold time,
63//! and computes a boolean value to return.
64//! To see how this is implemented in practice,
65//! take a look at
66//! [EnvFollower](crate::follow::EnvFollower) and [EnvFollowerSpec](crate::follow::EnvFollowerSpec).
67//! 
68//! Most of the code in this library consists of implementations of `Gen` and `Proc`,
69//! but we also include some miscellaneous utils in the [util](crate::util) mod.
70//! 
71//! ## Approach
72//! 
73//! The components in this library are implemented according to the following constraints:
74//! - keep memory use low
75//! - but prefer caching values over recomputing them
76//! - avoid allocations during processing
77//! - perform as few operations as possible during processing
78//! - maintain a strict boundary between user-modifiable parameters and internal mutable state
79//! - minimize dependencies
80//! - avoid unsafe code
81//! 
82//! As well as a few more relaxed constraints:
83//! - avoid newtypes, but use type aliases where it aids readability
84//! - use `f64` as the default float type
85//! - prefer use of phase offsets to pitches in Hz to minimize computations (see [pitch](crate::pitch))
86//! - in *simple* code, bounds checks can be skipped in release builds to aid performance
87//! - allow some unnecessary memory use if it's small and keeps code cleaner
88//! - keep components as generic as feasible, but allow some opinionated decisions
89//!   (see [ExpAdsrSpec](crate::env::ExpAdsrSpec))
90//! 
91//! ## Future
92//! 
93//! In general, we put code in this library as soon as we can find a way to make it generic enough
94//! to be useful for more than one type of plugin (and write adequate tests for it).
95//! Expect the functionality to expand as we clean up application-specific code
96//! and make it more modular.
97//! 
98//! The following additions are in the works, but not fully tested or cleaned up:
99//! 
100//! - First class support for plugin parameters
101//! - Ring buffers, delay lines, and pitch-shifting
102//! - Distortion algorithms
103//! - Additional filter types
104
105pub mod clock;
106pub mod env;
107pub mod filter;
108pub mod follow;
109pub mod lfo;
110pub mod modulate;
111pub mod osc;
112pub mod phase;
113pub mod pitch;
114pub mod trigger;
115pub mod util;
116pub mod wave;
117
118// TYPE ALIASES for clarity //
119
120/// Basic float type (currently `f64` but this may change).
121pub type F = f64;
122/// Anything measured in Hz; positive only.
123pub type Hz = F;
124/// Phase position of a wave; 0.0..1.0.
125pub type Phase = F;
126/// Amplitude of a sample; -1.0..=1.0.
127pub type Sample = F;
128/// Anything measured in fractions.
129pub type Scale = F;
130/// Anything measured in seconds; positive only, probably.
131pub type Seconds = F;
132
133/// A midi note; 0..128.
134pub type Note = u8;
135/// Midi velocity; 0..128.
136pub type Vel = u8;
137
138/// A processor that generates a value, with no input.
139///
140/// Notable implementations:
141/// - [Adsr](env::Adsr): generate a gain
142/// - [BasicLfo](lfo::BasicLfo): generate a sample
143///
144/// To take a value as input, use [Proc] instead.
145pub trait Gen<O> {
146    /// Type that holds this component's user-modifiable parameters
147    type Spec;
148    fn gen(&mut self, spec: &Self::Spec) -> O;
149}
150
151/// A processor that transforms a value.
152///
153/// Notable implementations:
154/// - [SvfProc](filter::svf::SvfProc): filter a sample
155/// - [OscCore](osc::OscCore): transform a pitch into a sample
156/// - [EnvFollower](follow::EnvFollower): transform a sample into a bool representing
157///   whether the envelope is on or off
158///
159/// If the processor has no meaningful processing-time input value, use [Gen] instead.
160pub trait Proc<I, O> {
161    /// Type that holds this component's user-modifiable parameters
162    type Spec;
163    fn proc(&mut self, spec: &Self::Spec, input: I) -> O;
164}
165
166// works out to about 2 samples at 44.1kHz; useful for tests:
167#[cfg(test)]
168const SHORT_TIME: Seconds = 0.00005;