From b5b1216167e5c93dd5c2cb97b303900d3e8e9990 Mon Sep 17 00:00:00 2001 From: Erik Natanael Gustafsson Date: Thu, 18 Jan 2024 12:12:21 +0100 Subject: [PATCH] Prepare for release --- CHANGES.md | 7 +- README.md | 71 ++++++++++++++++- knyst/examples/filtered_noise.rs | 127 ------------------------------- knyst_macro/README.md | 5 ++ 4 files changed, 80 insertions(+), 130 deletions(-) delete mode 100644 knyst/examples/filtered_noise.rs create mode 100644 knyst_macro/README.md diff --git a/CHANGES.md b/CHANGES.md index 5effa56..b70c725 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,15 +2,18 @@ ## Current changes +## v0.5.0 + Large breaking changes in this version -- New Handle system for ergonomically building and interacting with the graph! +- New Handle API for ergonomically building and interacting with the graph! +- A modal thread local interface to interact with knyst, making the Handle system possible, accessed through `knyst_commands()`. - NodeAddress was replaced by NodeId which is Copy. - Enabled routing a graph's inputs to its outputs directly, mostly for completeness of the API. - Fixed bug where input from graph inputs would overwrite other inputs to a node. - Updated all dependencies to the latest version. - New `impl_gen` macro for implementing the Gen trait and adding a Handle with input dependent methods and with much less error prone boilerplate. -- Fixed bug in the `Wavetable` which produced artefacts. +- Fixed bug in `Wavetable` which produced artefacts. - Refactored the codebase to something more logical. Many paths have changed, but importing the prelude should give you most of what you need. ## v0.4.3 diff --git a/README.md b/README.md index c505312..5b90d7b 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,79 @@ Knyst is a real time audio synthesis framework focusing on flexibility and performance. It's main target use case is desktop multi-threaded environments, but it can also do single threaded and/or non real time synthesis. Embedded platforms are currently not supported, but on the roadmap. +> [!IMPORTANT] +> Knyst is not stable. Knyst's API is still developing and can vary wildly between releases. + ## The name "Knyst" is a Swedish word meaning _very faint sound_. It is normally almost exclusively used in the negative e.g. "Det hörs inte ett knyst" (eng. "You cannot hear a sound"), but I think it's well worth some affirmative use. +# Examples + +## Play a sine wave + +```rust +// Set up knyst +let mut backend = CpalBackend::new(CpalBackendOptions::default())?; +let _sphere = KnystSphere::start( + &mut backend, + SphereSettings { + num_inputs: 1, + num_outputs: 2, + ..Default::default() + }, + print_error_handler, +); +// Add a sine wave to the top level graph +let sine_osc_handle = oscillator(WavetableId::cos()).freq(200.); +// Output it to the graph output, which for the top level graph is the +// application output. +graph_output(0, sine_osc_handle); +``` + +## Implement your own `Gen` + +Using the `impl_gen` macro takes care of most of the boiler plate. However, if you need a variable number of inputs or outputs +you have to implement the `Gen` trait yourself. + +```rust +struct DummyGen { + counter: Sample, +} +#[impl_gen] +impl DummyGen { + // Having a `new` method enabled generation of a function `dummy_gen() -> Handle` + pub fn new() -> Self { + Self { + counter: 0., + } + } + + /// The process method, or any method with the `#[process]` attribute is what will be run every block + /// when processing audio. It must not allocate or wait (unless you are doing non-realtime stuff). + /// + /// Inputs are extracted from any argument with the type &[Sample] + /// Outputs are extracted from any argument with the type &mut [Sample] + /// + /// Additionally, &[Trig] is an input channel of triggers and enables the `inputname_trig()` method on the handle + /// to conveniently schedule a trigger. `SampleRate` gives you the current sample rate and `BlockSize` gives you + /// the block size. + #[process] + fn process(&mut self, counter: &[Sample], output: &mut [Sample]) -> GenState { + for (count, out) in counter.iter().zip(output.iter_mut()) { + self.counter += 1.; + *out = count + self.counter; + } + GenState::Continue + } + /// The init method will be called before adding the `Gen` to a running graph. Here is where you can + /// allocate buffers and initialise state based on sample rate and block size. + fn init(&mut self, _sample_rate: SampleRate, _block_size: BlockSize, _node_id: NodeId) { + self.counter = 0.; + } +} +``` + # Features - good runtime performance @@ -13,7 +82,7 @@ Knyst is a real time audio synthesis framework focusing on flexibility and perfo - sample accurate node and parameter change scheduling to the graph - interopability with static Rust DSP libraries e.g. dasp and fundsp (they can be encapsulated in a node and added to the graph) - graphs can be nodes and changes can be applied to graphs within graphs at any depth -- feedback connections to get a 1 block delayed output of a node +- feedback connections to get a 1 block delayed output of a node to an earlier node in the chain - any number of inputs/outputs from a node - allows inner graphs with different block sizes using automatic buffering where necessary - choose your level of flexibility and optimisation: create a graph with lots of interconnected nodes or hard code it all into one node when you need more performance diff --git a/knyst/examples/filtered_noise.rs b/knyst/examples/filtered_noise.rs deleted file mode 100644 index 3007f97..0000000 --- a/knyst/examples/filtered_noise.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::time::Duration; - -use anyhow::Result; -#[allow(unused)] -use knyst::{ - audio_backend::{CpalBackend, CpalBackendOptions, JackBackend}, - controller::print_error_handler, - envelope::Envelope, - handles::{graph_output, handle, Handle}, - modal_interface::knyst_commands, - prelude::{delay::static_sample_delay, *}, - sphere::{KnystSphere, SphereSettings}, -}; -use knyst::{ - envelope::envelope_gen, - gen::filter::svf::{svf_dynamic, svf_filter, SvfFilterType}, -}; -use rand::{thread_rng, Rng}; -fn main() -> Result<()> { - let mut backend = CpalBackend::new(CpalBackendOptions::default())?; - // let mut backend = JackBackend::new("Knyst<3JACK")?; - let _sphere = KnystSphere::start( - &mut backend, - SphereSettings { - num_inputs: 1, - num_outputs: 1, - ..Default::default() - }, - print_error_handler, - ); - - let mut rng = thread_rng(); - // loop { - // let freq = rng.gen_range(100.0..1000.0); - // let length = rng.gen_range(0.5..4.0); - // dbg!(freq, length); - // spawn_filtered_noise(freq, length); - // std::thread::sleep(Duration::from_secs_f32(length)); - // } - loop { - let mut chord = [1.0, 5. / 4., 3. / 2., 17. / 8., 7. / 4.]; - let freq = rng.gen_range(100.0..200.0); - for f in &mut chord { - *f *= freq; - } - changed_harmony_chord(&chord); - - std::thread::sleep(Duration::from_secs_f32(10.)); - } - - // graph_output(0, white_noise()); - - // graph_output(0, (sine(wt).freq(200.)).repeat_outputs(1)); - - // Wait for ENTER - println!("Press ENTER to exit"); - let mut input = String::new(); - std::io::stdin().read_line(&mut input)?; - Ok(()) -} - -fn spawn_filtered_noise(freq: f32, length: f32) { - let mut rng = thread_rng(); - let filtered_noise = upload_graph(knyst_commands().default_graph_settings(), || { - let env = envelope_gen( - 0.0, - vec![ - (1.0, rng.gen_range(0.7..1.9)), - (0.5, length * 0.34), - (0.0, length * 0.66), - ], - knyst::envelope::SustainMode::NoSustain, - StopAction::FreeGraph, - ); - let source = pink_noise(); - let sig = svf_filter( - SvfFilterType::Band, - freq, - rng.gen_range(2000.0..10000.), - 0.0, - ) - .input(source); - let sig = sig * env * 0.01; - graph_output(0, sig.channels(2)); - }); - graph_output(0, filtered_noise); -} - -fn changed_harmony_chord(new_chord: &[f32]) { - let mut rng = thread_rng(); - if rng.gen::() > 0.4 { - for f in new_chord { - let length = rng.gen_range(6.0..14.0); - let speaker = rng.gen_range(0..4); - let filtered_noise = upload_graph(knyst_commands().default_graph_settings(), || { - let env = envelope_gen( - 0.0, - vec![(1.0, 3.), (1.0, length - 5.), (0.0, 2.)], - knyst::envelope::SustainMode::NoSustain, - StopAction::FreeGraph, - ); - let source = white_noise(); - let mut sigs = vec![]; - for i in 0..5 { - let freq_detune = [1.0, 1.001, 0.999, 1.002, 0.998][i]; - let q_env = envelope_gen( - 1.0 / rng.gen_range(0.001..0.008), - vec![(1. / 0.0003, length)], - knyst::envelope::SustainMode::NoSustain, - StopAction::Continue, - ); - - let sig = svf_dynamic(SvfFilterType::Band) - .cutoff_freq(f * freq_detune) - .q(q_env) - .gain(0.0) - .input(source); - sigs.push(sig); - } - let sig = sigs[0] + sigs[1] + sigs[2] + sigs[3] + sigs[4]; - let sig = sig * env * 0.01; - graph_output(speaker, sig); - }); - graph_output(0, filtered_noise); - } - } -} diff --git a/knyst_macro/README.md b/knyst_macro/README.md new file mode 100644 index 0000000..a6eb17f --- /dev/null +++ b/knyst_macro/README.md @@ -0,0 +1,5 @@ +# knyst_macro + +Macros to improve ergonomics for [Knyst](https://github.com/ErikNatanael/knyst). See the knyst documentation for more information. + +Versions follow the main knyst crate. \ No newline at end of file