Skip to content

Commit

Permalink
Update examples (#29)
Browse files Browse the repository at this point in the history
* Add more examples that are short and to the point
  • Loading branch information
Tuurlijk authored Nov 11, 2024
1 parent 1a16a7c commit d57d9d5
Show file tree
Hide file tree
Showing 14 changed files with 616 additions and 61 deletions.
51 changes: 42 additions & 9 deletions knyst/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,29 +81,62 @@ path = "macro_tests/macro_tests_main.rs"
name = "large_sine_graph"
harness = false

# Basic examples
[[example]]
name = "beat_callbacks"
path = "examples/beat_callbacks.rs"
name = "tone"
path = "examples/basic/tone.rs"
required-features = ["cpal"]

[[example]]
name = "simple_example"
path = "examples/simple_example.rs"
name = "tones"
path = "examples/basic/tones.rs"
required-features = ["cpal"]

[[example]]
name = "more_advanced_example"
path = "examples/more_advanced_example.rs"
name = "modulation"
path = "examples/basic/modulation.rs"
required-features = ["cpal"]

[[example]]
name = "interactive"
path = "examples/interactive.rs"
name = "adjust_frequency"
path = "examples/basic/adjust_frequency.rs"
required-features = ["cpal"]

[[example]]
name = "sound_file_playback"
path = "examples/sound_file_playback.rs"
path = "examples/basic/sound_file_playback.rs"
required-features = ["cpal"]

[[example]]
name = "scheduling"
path = "examples/basic/scheduling.rs"
required-features = ["cpal"]

# Envelope examples
[[example]]
name = "volume_envelope"
path = "examples/envelopes/volume_envelope.rs"
required-features = ["cpal"]

[[example]]
name = "frequency_envelope"
path = "examples/envelopes/frequency_envelope.rs"
required-features = ["cpal"]

# Advanced examples
[[example]]
name = "beat_callbacks"
path = "examples/beat_callbacks.rs"
required-features = ["cpal"]

[[example]]
name = "more_advanced_example"
path = "examples/more_advanced_example.rs"
required-features = ["cpal"]

[[example]]
name = "interactive"
path = "examples/interactive.rs"
required-features = ["cpal"]

[[example]]
Expand Down
123 changes: 115 additions & 8 deletions knyst/examples/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,123 @@
# Knyst examples

All the examples currently use either the JACK or the CPAL backend. That means that to run them you need to enable that feature, e.g.
List all available examples using:

```sh
cargo run --release --example interactive --features cpal
```shell
cargo run --example
```

## Basic examples

### Tone

This example plays a single tone

```shell
cargo run --example tone
```

### Tones

This example plays two tones. One in the left channel and one in the right channel.

```shell
cargo run --example tones
```

### Modulation

This example plays a tone modulated by a oscillator.

```shell
cargo run --example modulation
```

## Interactive
### Adjust frequency

This example plays a different tone in each channel, both with different modulators. You can change the frequency of the tone in the left channel by entering a numerical value.

```shell
cargo run --example adjust_frequency
```

### Sound file playback

Plays back 10 seconds of an audio file chosen by the user.

```shell
cargo run --example sound_file_playback
```

### Schedule a tone

Plays back a tone after 3 seconds of silence.

```shell
cargo run --example scheduling
```

## Envelopes

### Volume envelope

This example plays a tone with a volume envelope.

```shell
cargo run --example volume_envelope
```

### Frequency envelope

This example plays a tone with a frequency envelope.

```shell
cargo run --example frequency_envelope
```

## Advanced

### Beat Callbacks

The main function initializes and starts the audio processing system with the default settings. It sets up a graph with wavetables, modulators, and amplitude modulators, and schedules beat-accurate parameter changes. The function reads user input to allow interaction with the callback and offers options to stop the callback or quit the program.

```shell
cargo run --example beat_callbacks
```

### Filter

```shell
cargo run --example filter_test
```

### Interactive

This example aims to provide an overview of different ways that Knyst can be used. The example currently demonstrates:

- async using tokio
- interactivity using the keyboard to play pitches monophonically
- sound file playback
- replacing a wavetable in Resources while everything is running
- starting an audio backend
- pushing nodes
- making connections
- inner graphs
- async and multi threaded usage of KnystCommands
- scheduling changes
- interactivity
- wrapping other dsp libraries (fundsp in this case)
- writing a custom error handler

```shell
cargo run --example interactive
```

### More advanced example

```shell
cargo run --example more_advanced_example
```

## Using JACK

All the examples currently use either the JACK or the CPAL backend. If you want to use JACK, add that as a feature flag. Also uncomment the JACK backend line in the example and comment out the CPAL backend line.

```sh
cargo run --example filter_test --features jack
```
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,30 @@ use knyst::{
prelude::*,
};

/// Sets up both an owned and shared wavetable oscillator, and connects them to audio output channels.
///
/// Listens for user input to dynamically adjust the frequency of the first oscillator.
///
fn main() -> Result<()> {
let mut backend = CpalBackend::new(CpalBackendOptions::default())?;
// Uncomment the line below and comment the line above to use the JACK backend instead
// let mut backend = JackBackend::new("Knyst<3JACK")?;

let sample_rate = backend.sample_rate() as Sample;
let block_size = backend.block_size().unwrap_or(64);
println!("sr: {sample_rate}, block: {block_size}");
let _backend = setup();

// Start with an automatic helper thread for scheduling changes and managing resources.
// If you want to manage the `Controller` yourself, use `start_return_controller`.
let _sphere = KnystSphere::start(
&mut backend,
SphereSettings {
num_inputs: 1,
num_outputs: 2,
..Default::default()
},
print_error_handler,
);
// Owned wavetable oscillator at 440 Hz
let node0 = wavetable_oscillator_owned(Wavetable::sine()).freq(440.);
// We can also use a shared wavetable oscillator which reads its wavetable from `Resources`. A cosine wavetable
// We can also use a shared wavetable oscillator which reads it's wavetable from `Resources`. A cosine wavetable
// is created by default.
let modulator = oscillator(WavetableId::cos()).freq(5.);
// Output to the zeroth (left) channel
graph_output(0, node0 * (modulator * 0.25 + 0.5));

let node1 = wavetable_oscillator_owned(Wavetable::sine()).freq(220.);
let modulator = oscillator(WavetableId::cos()).freq(3.);
// Output a different sound to the first (right) channel
graph_output(1, node1 * (modulator * 0.25 + 0.5));

// Respond to user input. This kind of interaction can be put in a different thread and/or in an async runtime.
println!("Playing a sine wave with 440 Hz and 220 Hz");
println!("Enter a new frequency for the left channel followed by [ENTER]");
println!("Press [q] to quit");
let mut input = String::new();
loop {
match std::io::stdin().read_line(&mut input) {
Expand All @@ -57,3 +48,25 @@ fn main() -> Result<()> {
}
Ok(())
}

/// Initializes the audio backend and starts a `KnystSphere` for audio processing.
/// Start with an automatic helper thread for scheduling changes and managing resources.
/// If you want to manage the `Controller` yourself, use `start_return_controller`.
///
/// The backend is returned here because it would otherwise be dropped at the end of setup()
fn setup() -> impl AudioBackend {
let mut backend =
CpalBackend::new(CpalBackendOptions::default()).expect("Unable to connect to CPAL backend");
// Uncomment the line below and comment the line above to use the JACK backend instead
// let mut backend = JackBackend::new("Knyst<3JACK").expect("Unable to start JACK backend");

let _sphere_id = KnystSphere::start(
&mut backend,
SphereSettings {
..Default::default()
},
print_error_handler,
)
.ok();
backend
}
57 changes: 57 additions & 0 deletions knyst/examples/basic/modulation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use anyhow::Result;
#[allow(unused)]
use knyst::{
audio_backend::{CpalBackend, CpalBackendOptions, JackBackend},
controller::print_error_handler,
prelude::*,
};

/// Configures a wavetable oscillator to output modulated audio.
///
/// The function performs the following steps:
/// 1. Creates an owned wavetable oscillator set at 110 Hz.
/// 2. Outputs the oscillator to the left and right channels at 30% volume each, modulating it with a frequency of 2 Hz.
/// 3. Waits for user to press ENTER.
///
/// The modulation signal is a sine which has a range of values between -1 and 1. To prevent
/// zero-crossings, the signal is shifted up by dividing by 2 (so the range becomes -0.5 to 0.5) and
/// then adding 0.5 (so the range becomes 0 to 1).
///
fn main() -> Result<()> {
let _backend = setup();

let node0 = wavetable_oscillator_owned(Wavetable::sine()).freq(110.);

let modulator = oscillator(WavetableId::cos()).freq(2.);

graph_output(0, node0 * 0.3 * (modulator * 0.5 + 0.5));
graph_output(1, node0 * 0.3 * (modulator * 0.5 + 0.5));

println!("Playing a modulated sine wave at 110 Hz at an amplitude of 0.3");
println!("Press [ENTER] to exit");
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(())
}

/// Initializes the audio backend and starts a `KnystSphere` for audio processing.
/// Start with an automatic helper thread for scheduling changes and managing resources.
/// If you want to manage the `Controller` yourself, use `start_return_controller`.
///
/// The backend is returned here because it would otherwise be dropped at the end of setup()
fn setup() -> impl AudioBackend {
let mut backend =
CpalBackend::new(CpalBackendOptions::default()).expect("Unable to connect to CPAL backend");
// Uncomment the line below and comment the line above to use the JACK backend instead
// let mut backend = JackBackend::new("Knyst<3JACK").expect("Unable to start JACK backend");

let _sphere_id = KnystSphere::start(
&mut backend,
SphereSettings {
..Default::default()
},
print_error_handler,
)
.ok();
backend
}
59 changes: 59 additions & 0 deletions knyst/examples/basic/scheduling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use anyhow::Result;
use knyst::controller::schedule_bundle;
use knyst::graph;
#[allow(unused)]
use knyst::{
audio_backend::{CpalBackend, CpalBackendOptions, JackBackend},
controller::print_error_handler,
prelude::*,
};

/// Configures a wavetable oscillator to output audio after a number of seconds.
///
/// The function performs the following steps:
/// 1. Creates an owned wavetable oscillator set at 110 Hz.
/// 2. Wait 3 seconds
/// 3. Outputs the oscillator to the left and right channels at 30% volume each.
/// 4. Waits for user to press ENTER.
///
/// Scheduling is currently limited to changes of constant values and spawning new nodes, not new
/// connections. This means you need to place all node and envelope creation inside the
/// schedule_bundle cAll.
///
fn main() -> Result<()> {
let _backend = setup();

schedule_bundle(graph::Time::Seconds(Seconds::new(3, 0)), || {
let node0 = wavetable_oscillator_owned(Wavetable::sine()).freq(110.);
graph_output(0, node0 * 0.3);
graph_output(1, node0 * 0.3);
});

println!("Playing a sine wave at 110 Hz at an amplitude of 0.3, after 3 seconds");
println!("Press [ENTER] to exit");
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(())
}

/// Initializes the audio backend and starts a `KnystSphere` for audio processing.
/// Start with an automatic helper thread for scheduling changes and managing resources.
/// If you want to manage the `Controller` yourself, use `start_return_controller`.
///
/// The backend is returned here because it would otherwise be dropped at the end of setup()
fn setup() -> impl AudioBackend {
let mut backend =
CpalBackend::new(CpalBackendOptions::default()).expect("Unable to connect to CPAL backend");
// Uncomment the line below and comment the line above to use the JACK backend instead
// let mut backend = JackBackend::new("Knyst<3JACK").expect("Unable to start JACK backend");

let _sphere_id = KnystSphere::start(
&mut backend,
SphereSettings {
..Default::default()
},
print_error_handler,
)
.ok();
backend
}
Loading

0 comments on commit d57d9d5

Please sign in to comment.