knyst/
lib.rs

1//! # Knyst - audio graph and synthesis library
2//!
3//! Knyst is a real time audio synthesis framework focusing on flexibility and
4//! performance. It's main target use case is desktop multi-threaded real time
5//! environments, but it can also do single threaded and/or non real time
6//! synthesis. Embedded platforms are currently not supported, but on the
7//! roadmap.
8//!
9//! The main selling point of Knyst is that the graph can be modified while it's
10//! running: nodes and connections between nodes can be added/removed. It also
11//! supports shared resources such as wavetables and buffers.
12//!
13//! ## Status
14//!
15//! Knyst is in its early stages. Expect large breaking API changes between
16//! versions.
17//!
18//! ## The name
19//!
20//! "Knyst" is a Swedish word meaning _very faint sound_.
21//!
22//! ## Architecture
23//!
24//! The core of Knyst is the [`Graph`] struct and the [`Gen`] trait. [`Graph`]s
25//! can have nodes containing anything that implements [`Gen`]. [`Graph`]s
26//! can also themselves be added as a node.
27//!
28//! Nodes in a running [`Graph`] can be freed or signal to the [`Graph`]
29//! that they or the entire [`Graph`] should be freed. [`Connection`]s between
30//! Nodes and the inputs and outputs of a [`Graph`] can also be changed
31//! while the [`Graph`] is running. This way, Knyst acheives a similar
32//! flexibility to SuperCollider.
33//!
34//! It is easy to get things wrong when using a [`Graph`] as a [`Gen`] directly
35//! so that functionality is encapsulated. For the highest level [`Graph`] of
36//! your program you may want to use [`RunGraph`] to get a node which
37//! you can run in a real time thread or non real time to generate samples.
38//! Using the [`audio_backend`]s this process is automated for you.
39//!
40//! ## Features
41//!
42//! - *unstable*: Enables unstable optimisations in some cases that currently requires nightly.
43//! - *assert_no_alloc*: (default) Panics in debug builds if an allocation is detected on the audio thread.
44//! - *debug-warn-on-alloc*: Print a warning instead of panicing when allocating on the audio thread (debug build only).
45//! - *serde-derive*: Enables some data structures to be serialized/deserialized using serde.
46//! - *cpal*: (default) Enables the cpal AudioBackend
47//! - *jack*: (default) Enables the JACK AudioBackend
48//!
49#![deny(rustdoc::broken_intra_doc_links)] // error if there are broken intra-doc links
50#![warn(missing_docs)]
51// #![warn(clippy::pedantic)]
52#![cfg_attr(feature = "unstable", feature(portable_simd))]
53
54#[allow(unused)]
55use crate::audio_backend::AudioBackend;
56#[allow(unused)]
57use crate::gen::Gen;
58#[allow(unused)]
59use crate::sphere::KnystSphere;
60use audio_backend::AudioBackendError;
61use core::fmt::Debug;
62use modal_interface::SphereError;
63use resources::ResourcesError;
64use std::ops::{Deref, DerefMut};
65// Import these for docs
66#[allow(unused_imports)]
67use graph::{Connection, Graph, RunGraph};
68pub use modal_interface::knyst_commands;
69pub use resources::Resources;
70
71// assert_no_alloc to make sure we are not allocating on the audio thread. The
72// assertion is put in AudioBackend.
73#[allow(unused_imports)]
74#[cfg(all(debug_assertions, feature = "assert_no_alloc"))]
75use assert_no_alloc::*;
76
77#[cfg(all(debug_assertions, feature = "assert_no_alloc"))]
78#[global_allocator]
79static A: AllocDisabler = AllocDisabler;
80
81pub mod audio_backend;
82pub mod buffer;
83pub mod controller;
84pub mod envelope;
85pub mod gen;
86pub mod graph;
87pub mod handles;
88pub mod inspection;
89mod internal_filter;
90pub mod modal_interface;
91pub mod node_buffer;
92pub mod offline;
93pub mod prelude;
94pub mod resources;
95pub mod scheduling;
96pub mod sphere;
97pub mod time;
98pub mod trig;
99pub mod wavetable;
100pub mod wavetable_aa;
101pub mod xorrng;
102
103/// Combined error type for Knyst, containing any other error in the library.
104#[derive(thiserror::Error, Debug)]
105pub enum KnystError {
106    /// Error making a connection inside a [`Graph`]
107    #[error("Error adding or removing connections: {0}")]
108    ConnectionError(#[from] graph::connection::ConnectionError),
109    /// Error freeing a node from a [`Graph`]
110    #[error("Error freeing a node: {0}")]
111    FreeError(#[from] graph::FreeError),
112    /// Error pushing a node to a [`Graph`]
113    #[error("Error pushing a node: {0}]")]
114    PushError(#[from] graph::PushError),
115    /// Error scheduling a change
116    #[error("Error scheduling a change: {0}")]
117    ScheduleError(#[from] graph::ScheduleError),
118    /// Error from creating a RunGraph
119    #[error("Error with the RunGraph: {0}")]
120    RunGraphError(#[from] graph::run_graph::RunGraphError),
121    /// Error from interacting with [`Resources`]
122    #[error("Resources error : {0}")]
123    ResourcesError(#[from] ResourcesError),
124    /// Error from interacting with [`KnystSphere`] or any of the modal command functions
125    #[error("Sphere error : {0}")]
126    SphereError(#[from] SphereError),
127    /// Error from interacting with an [`AudioBackend`].
128    #[error("Audio backend error : {0}")]
129    AudioBackendError(#[from] AudioBackendError),
130}
131
132/// Convert db to amplitude
133#[inline]
134#[must_use]
135pub fn db_to_amplitude(db: Sample) -> Sample {
136    (10.0 as Sample).powf(db / 20.0)
137}
138/// Convert amplitude to db
139#[inline]
140#[must_use]
141pub fn amplitude_to_db(amplitude: Sample) -> Sample {
142    20.0 * amplitude.log10()
143}
144
145/// The current sample type used throughout Knyst
146pub type Sample = f32;
147
148/// Marker for inputs that are trigs. This makes it possible to set that value correctly through a Handle.
149pub type Trig = Sample;
150
151/// Newtype for a sample rate to identify it in function signatures. Derefs to a `Sample` for easy use on the audio thread.
152#[derive(Copy, Clone, Debug)]
153pub struct SampleRate(pub Sample);
154
155impl SampleRate {
156    #[inline(always)]
157    #[allow(missing_docs)]
158    pub fn to_f64(self) -> f64 {
159        self.0 as f64
160    }
161    #[allow(missing_docs)]
162    #[inline(always)]
163    pub fn to_usize(self) -> usize {
164        self.0 as usize
165    }
166}
167
168impl Deref for SampleRate {
169    type Target = Sample;
170
171    fn deref(&self) -> &Self::Target {
172        &self.0
173    }
174}
175
176impl DerefMut for SampleRate {
177    fn deref_mut(&mut self) -> &mut Self::Target {
178        &mut self.0
179    }
180}
181
182impl From<SampleRate> for f64 {
183    fn from(value: SampleRate) -> Self {
184        value.0 as f64
185    }
186}
187
188impl From<f32> for SampleRate {
189    fn from(value: f32) -> Self {
190        Self(value as Sample)
191    }
192}
193impl From<f64> for SampleRate {
194    fn from(value: f64) -> Self {
195        Self(value as Sample)
196    }
197}
198
199#[derive(Copy, Clone, Debug)]
200/// BlockSize.
201///
202/// Can be an unorthodox block size value in the event of a partial block at the beginning of a node's existence in the graph.
203pub struct BlockSize(pub usize);
204
205impl From<BlockSize> for usize {
206    #[inline(always)]
207    fn from(value: BlockSize) -> Self {
208        value.0
209    }
210}
211impl From<usize> for BlockSize {
212    fn from(value: usize) -> Self {
213        Self(value)
214    }
215}
216
217impl Deref for BlockSize {
218    type Target = usize;
219
220    fn deref(&self) -> &Self::Target {
221        &self.0
222    }
223}
224
225impl DerefMut for BlockSize {
226    fn deref_mut(&mut self) -> &mut Self::Target {
227        &mut self.0
228    }
229}