pointillism/
lib.rs

1//! A compositional library for musical composition.
2//!
3//! # Examples
4//!
5//! If you want to see pointillism in action and what it's capable of, run the examples in the
6//! `examples` folder. There's also many simple examples scattered throughout the source code,
7//! showing off different features.
8//!
9//! For a starting example, see the [`create`] docs.
10//!
11//! **Note:** Some examples may be loud, dissonant, and/or jarring. Hearing discretion is advised.
12//!
13//! # Design
14//!
15//! The default way in which pointillism outputs audio is by writing sample by sample into a 32-bit
16//! floating point `.wav` file. Internal calculations use 64-bit floating points.
17//!
18//! For convenience, the [`Signal`] trait is provided. Types implementing this trait generate sample
19//! data frame by frame. If the type also implements [`SignalMut`], it can be advanced or
20//! retriggered.
21//!
22//! Signals may be composed to create more complex signals, using for instance the [`eff::MapSgn`]
23//! and [`eff::MutSgn`] structs. Moreover, you can implement the [`Signal`] and [`SignalMut`] traits
24//! for your own structs, giving you vast control over the samples you're producing.
25//!
26//! ## Naming scheme
27//!
28//! The `pointillism` code has a lot of moving parts, and a bunch of similarly named types. Because
29//! of this, we rely on the `prelude` to categorize things neatly.
30//!
31//! Every type has a three-letter namespace which helps categorizes it. The main namespaces are as
32//! follows:
33//!
34//! | Namespace | Full Name | Contents |
35//! |-|-|-|
36//! | [`buf`] | `buffers` | Audio buffers and associated traits.
37//! | [`crv`] | `curves` | Basic oscillator shapes, and builder methods for more complex ones (in the future).
38//! | [`ctr`] | `control` | Control structures, which allow for events to happen at specified time intervals.
39//! | [`eff`] | `effects` | For effects, meaning types that alter other signals.
40//! | [`map`] | `map` | Basic maps and associated traits.
41//! | [`gen`] | `generators` | Types that generate a signal "on their own". This includes the basic oscillators like [`gen::Loop`] and [`gen::Once`].
42//! | [`sgn`] | `signal` | Traits on signals, including the basic [`Signal`] and [`SignalMut`].
43//! | [`smp`] | `smp` | Basic traits and types for sample types, including [`smp::Mono`] and [`smp::Stereo`].
44//! | [`rtn`] | `routing` | Structures for mixing or combining different signals together.
45//! | [`unt`] | `units` | Different units for musical measurement, and associated arithmetical boilerplate.
46//!
47//! Note that traits are always imported when the prelude is imported. This simplifies some complex
48//! `impl` declarations, and also makes the trait methods available whenever.
49//!
50//! Some of these namespaces also contain further nested namespaces, almost always three letters.
51//! See the documentation for the full breakdown.
52//!
53//! ## Compile-time
54//!
55//! You can think of pointillism as a compile-time modular synthesizer, where every new struct is
56//! its own module.
57//!
58//! Advantages of this design are extensibility and generality. It's relatively easy to create a
59//! highly customizable and complex signal with many layers by composing some functions together.
60//!
61//! The downside is that these synths end up having unwieldy type signatures. Moreso, it's really
62//! hard to build synths in real time.
63//!
64//! ## Features
65//!
66//! The project uses the following features:
67//!
68//! | Feature | Enables |
69//! |-|-|
70//! | [`hound`](https://docs.rs/hound/latest/hound)* | Saving songs as WAV files. |
71//! | [`cpal`](https://docs.rs/cpal/latest/cpal) | Playing songs in a dedicated thread. |
72//! | [`midly`](https://docs.rs/midly/latest/midly) | Reading and playing back MIDI files. |
73//! | [`human-duration`](https://docs.rs/human-duration/latest/human_duration)* | Pretty-printing for the [`unt::RawTime`] type. |
74//!
75//! \* Features marked with an asterisk are enabled by default.
76//!
77//! # Goals
78//!
79//! Future goals of pointillism are:
80//!
81//! - (Better) algorithmic reverbs
82//! - Limiters, compressors, sidechaining
83//! - [Me](https://viiii.bandcamp.com) making a whole album with it :D
84//!
85//! # Disclaimer
86//!
87//! This is a passion project made by one college student learning about DSP. I make no guarantees
88//! on it being well-designed, well-maintained, or usable for your own goals.
89//!
90//! If you just want to make music with code, and especially if you enjoy live feedback,
91//! [SuperCollider](https://supercollider.github.io) and [Pure Data](https://puredata.info) will
92//! most likely be better alternatives for you.
93//!
94//! That said, if you happen to stumble across this and make something cool, please let me know!
95
96#![warn(clippy::cargo)]
97#![warn(clippy::missing_docs_in_private_items)]
98#![warn(clippy::pedantic)]
99#![allow(clippy::module_name_repetitions)]
100
101pub mod buffers;
102pub mod control;
103pub mod curves;
104pub mod effects;
105pub mod generators;
106pub mod map;
107pub mod routing;
108pub mod sample;
109pub mod signal;
110pub mod units;
111
112#[cfg(feature = "cpal")]
113pub mod cpal;
114#[cfg(feature = "hound")]
115pub use with_hound::*;
116
117// Needed so that the docs render properly.
118use crate::prelude::*;
119
120/// A generic "out of bounds" error message.
121pub const OOB: &str = "index out of bounds";
122
123/// Increments a value in `0..len` by one, and wraps it around.
124///
125/// This should be marginally more efficient than `value = (value + 1) % len`, as it avoids the more
126/// costly modulo operation.
127pub(crate) fn mod_inc(len: usize, value: &mut usize) {
128    *value += 1;
129
130    if *value == len {
131        *value = 0;
132    }
133}
134
135/// A trait for some function that returns samples frame by frame.
136pub trait SongFunc {
137    /// The type of samples returned.
138    type Sample: Audio;
139
140    /// Gets the next sample.
141    fn eval(&mut self, time: unt::Time) -> Self::Sample;
142}
143
144/// Wraps a `FnMut(unt::Time) -> A` so that it implements the [`SongFunc`] trait.
145pub struct Func<T>(T);
146
147/// Wraps a [`SignalMut`] so that it implements the [`SongFunc`] trait.
148pub struct Sgn<S>(S);
149
150/// Wraps a [`&mut SignalMut`](SignalMut) so that it implements the [`SongFunc`] trait.
151pub struct SgnMut<'a, S>(&'a mut S);
152
153impl<A: Audio, T: FnMut(unt::Time) -> A> SongFunc for Func<T> {
154    type Sample = A;
155    fn eval(&mut self, time: units::Time) -> Self::Sample {
156        (self.0)(time)
157    }
158}
159
160impl<S: SignalMut> SongFunc for Sgn<S>
161where
162    S::Sample: Audio,
163{
164    type Sample = S::Sample;
165    fn eval(&mut self, _: units::Time) -> Self::Sample {
166        self.0.next()
167    }
168}
169
170impl<'a, S: SignalMut> SongFunc for SgnMut<'a, S>
171where
172    S::Sample: Audio,
173{
174    type Sample = S::Sample;
175    fn eval(&mut self, _: units::Time) -> Self::Sample {
176        self.0.next()
177    }
178}
179
180/// Represents a song, a piece of code that can be evaluated frame by frame to generate succesive
181/// samples. The duration of the file is exactly rounded down to the sample. The song will be mono
182/// or stereo, depending on whether the passed function returns [`smp::Mono`] or [`smp::Stereo`].
183///
184/// See the `examples` folder for example creations.
185///
186/// ## Example
187///
188/// We make the most basic song possible: a single sine wave.
189///
190/// ```
191/// # use pointillism::prelude::*;
192/// // Project sample rate.
193/// const SAMPLE_RATE: unt::SampleRate = unt::SampleRate::CD;
194///
195/// // File duration.
196/// let length = unt::Time::from_sec(1.0, SAMPLE_RATE);
197/// // Sine wave frequency.
198/// let freq = unt::Freq::from_hz(440.0, SAMPLE_RATE);
199///
200/// // We create a mono signal that loops through a sine curve at the specified frequency.
201/// let mut sgn = gen::Loop::<smp::Mono, _>::new(crv::Sin, freq);
202///
203/// // Export to file.
204/// Song::new_sgn(length, SAMPLE_RATE, &mut sgn).export("examples/sine.wav");
205/// ```
206pub struct Song<F: SongFunc> {
207    /// The length of the song in samples.
208    length: unt::Time,
209    /// The sample rate of the song.
210    sample_rate: unt::SampleRate,
211    /// The [`SongFunc`] that generates the song.
212    song: F,
213}
214
215impl<F: SongFunc> Song<F> {
216    pub const fn new_raw(length: unt::Time, sample_rate: unt::SampleRate, song: F) -> Self {
217        Self {
218            length,
219            sample_rate,
220            song,
221        }
222    }
223}
224
225impl<A: Audio, F: FnMut(unt::Time) -> A> Song<Func<F>> {
226    /// Creates a new [`Song`] from a function, taking the elapsed time as an argument. To instead
227    /// take in a signal, see [`Self::new_sgn`].
228    ///
229    /// The resulting WAV file will be mono or stereo, depending on whether the passed function
230    /// returns [`smp::Mono`] or [`smp::Stereo`].
231    ///
232    /// ## Example
233    ///
234    /// For an example, see the [`Song`] docs.
235    pub const fn new(length: unt::Time, sample_rate: unt::SampleRate, song: F) -> Self {
236        Self::new_raw(length, sample_rate, Func(song))
237    }
238}
239
240impl<'a, S: SignalMut> Song<SgnMut<'a, S>>
241where
242    S::Sample: Audio,
243{
244    /// A convenience function to create a [`new`](Self::new) song from a given signal. The signal
245    /// is not consumed. To instead take in a function, see [`Self::new`]. If you have to own the
246    /// song, use [`Self::new_sgn_owned`].
247    ///
248    /// The resulting WAV file will be mono or stereo, depending on whether the passed function
249    /// returns [`smp::Mono`] or [`smp::Stereo`].
250    ///
251    /// ## Example
252    ///
253    /// For an example, see the [`Song`] docs.
254    pub fn new_sgn(length: unt::Time, sample_rate: unt::SampleRate, sgn: &'a mut S) -> Self
255    where
256        S::Sample: Audio,
257    {
258        Self::new_raw(length, sample_rate, SgnMut(sgn))
259    }
260}
261
262impl<S: SignalMut> Song<Sgn<S>>
263where
264    S::Sample: Audio,
265{
266    /// A convenience function to create a [`new`](Self::new) song from a given signal. The signal
267    /// is consumed. To instead take in a function, see [`Self::new`]. If you don't have to own the
268    /// song, use [`Self::new_sgn`].
269    ///
270    /// The resulting WAV file will be mono or stereo, depending on whether the passed function
271    /// returns [`smp::Mono`] or [`smp::Stereo`].
272    ///
273    /// ## Example
274    ///
275    /// For an example, see the [`Song`] docs.
276    pub fn new_sgn_owned(length: unt::Time, sample_rate: unt::SampleRate, sgn: S) -> Self
277    where
278        S::Sample: Audio,
279    {
280        Self::new_raw(length, sample_rate, Sgn(sgn))
281    }
282}
283
284/// Methods that require [`hound`].
285#[cfg(feature = "hound")]
286mod with_hound {
287    use crate::prelude::*;
288
289    /// The [specification](hound::WavSpec) for the output file.
290    #[must_use]
291    pub const fn spec(channels: u8, sample_rate: unt::SampleRate) -> hound::WavSpec {
292        hound::WavSpec {
293            channels: channels as u16,
294            sample_rate: sample_rate.0,
295            bits_per_sample: 32,
296            sample_format: hound::SampleFormat::Float,
297        }
298    }
299
300    impl<F: SongFunc> Song<F> {
301        /// Exports a song as a WAV file. Requires the [`hound`] feature.
302        ///
303        /// ## Errors
304        ///
305        /// This should only return an error in the case of an IO error.
306        pub fn export_res<P: AsRef<std::path::Path>>(&mut self, filename: P) -> hound::Result<()> {
307            let length = self.length.samples.int();
308            let mut writer =
309                hound::WavWriter::create(filename, spec(F::Sample::size_u8(), self.sample_rate))?;
310
311            let mut time = unt::Time::ZERO;
312            for _ in 0..length {
313                self.song.eval(time).write(&mut writer)?;
314                time.advance();
315            }
316
317            writer.finalize()
318        }
319
320        /// A convenience function for calling [`Self::export_res`], panicking in case of an IO
321        /// error.
322        ///
323        /// ## Panics
324        ///
325        /// Panics in case of an IO error.
326        pub fn export<P: AsRef<std::path::Path>>(&mut self, filename: P) {
327            self.export_res(filename).expect("IO error");
328        }
329    }
330}
331
332/// The crate prelude.
333///
334/// See the readme for a full list of abbreviations.
335pub mod prelude {
336    // Abbreviate module names.
337    pub use crate::{
338        buffers as buf, control as ctr, curves as crv, effects as eff, generators as gen, map,
339        routing as rtn, sample as smp, signal as sgn, units as unt,
340    };
341
342    // Import traits.
343    pub use crate::{
344        buf::{Buffer, BufferMut, Ring},
345        eff::flt::FilterMap,
346        map::{Env, Map, Mut},
347        sgn::{Base, Done, Frequency, Panic, Signal, SignalMut, Stop},
348        smp::{Array, Audio, Sample, SampleBase},
349        Song, SongFunc,
350    };
351    pub(crate) use sgn::impl_base;
352}