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}