kira/
modulator.rs

1/*!
2Global values that parameters (like volume and playback rate) can be linked to.
3
4Any type that implements [`ModulatorBuilder`] can be added to an audio manager by
5using [`AudioManager::add_modulator`](crate::AudioManager::add_modulator).
6
7If needed, you can create custom modulators by implementing the [`ModulatorBuilder`]
8and [`Modulator`] traits.
9
10# Why modulators?
11
12Many properties of things in Kira, like the volumes of sounds, can be smoothly
13transitioned from one value to another without the use of modulators. Modulators
14become handy when:
15
16- You want to control multiple properties of objects in lockstep
17- You need to change a property in a way that's more complicated than a simple
18  transition
19
20# Tweener example
21
22Let's say we have a music track with two layers that play simultaneously:
23drums and piano. When the player character enters water, we want the music
24to sound "underwater", so we'll fade out the drums and make the piano sound
25more muffled using a low-pass filter.
26
27For this situation, a [`tweener`] is appropriate. Tweeners hold a value
28that doesn't change until we tell it to, and the value can be smoothly
29transitioned.
30
31The tweener is an input value that will generate multiple output values:
32the drums volume and piano filter frequency. When the tweener is set to
33`0.0`, that represents that the player is not underwater, and when it's
34`1.0`, the player is submerged. (These are arbitrary values.)
35
36| Tweener value | Drums volume | Piano filter frequency |
37|---------------|--------------|------------------------|
38| 0.0           | 1.0          | 20,000 Hz              |
39| 1.0           | 0.0          | 500 Hz                 |
40
41First, let's create the tweener:
42
43```no_run
44use kira::{
45	AudioManager, AudioManagerSettings, DefaultBackend,
46	modulator::tweener::TweenerBuilder,
47};
48
49let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
50let mut tweener = manager.add_modulator(TweenerBuilder { initial_value: 0.0 })?;
51# Result::<(), Box<dyn std::error::Error>>::Ok(())
52```
53
54Next, we'll create a mixer track with a low-pass filter effect. The piano will play
55on this track so we can make it sound more or less muffled.
56
57```no_run
58# use kira::{
59# 	AudioManager, AudioManagerSettings, DefaultBackend,
60# 	modulator::tweener::TweenerBuilder,
61# };
62use kira::{
63	effect::filter::FilterBuilder,
64	Mapping, Value, Easing,
65	track::TrackBuilder,
66};
67
68# let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
69# let mut tweener = manager.add_modulator(TweenerBuilder { initial_value: 0.0 })?;
70let filter_builder = FilterBuilder::new()
71	.cutoff(Value::from_modulator(&tweener, Mapping {
72		input_range: (0.0, 1.0),
73		output_range: (20_000.0, 500.0),
74		easing: Easing::Linear,
75	}));
76let mut piano_track = manager.add_sub_track(TrackBuilder::new().with_effect(filter_builder))?;
77# Result::<(), Box<dyn std::error::Error>>::Ok(())
78```
79
80We use a `Mapping` to map the input values of the tweener to the output values
81of the filter cutoff frequency.
82
83Finally, we'll play the sounds:
84
85```no_run
86# use kira::{
87# 	AudioManager, AudioManagerSettings, DefaultBackend,
88# 	modulator::tweener::TweenerBuilder,
89#   effect::filter::FilterBuilder,
90# 	track::TrackBuilder,
91# 	Mapping, Value, Easing,
92#   Decibels,
93# };
94use kira::sound::static_sound::StaticSoundData;
95
96# let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
97# let mut tweener = manager.add_modulator(TweenerBuilder { initial_value: 0.0 })?;
98# let filter_builder = FilterBuilder::new()
99# 	.cutoff(Value::from_modulator(&tweener, Mapping {
100# 		input_range: (0.0, 1.0),
101# 		output_range: (20_000.0, 500.0),
102# 		easing: Easing::Linear,
103# 	}));
104# let mut piano_track = manager.add_sub_track(TrackBuilder::new().with_effect(filter_builder))?;
105piano_track.play(StaticSoundData::from_file("piano.ogg")?)?;
106manager.play(
107	StaticSoundData::from_file("drums.ogg")?
108		.volume(Value::from_modulator(&tweener, Mapping {
109			input_range: (0.0, 1.0),
110			output_range: (Decibels::IDENTITY, Decibels::SILENCE),
111			easing: Easing::Linear,
112		}))
113)?;
114# Result::<(), Box<dyn std::error::Error>>::Ok(())
115```
116
117Notice how we also use a `Mapping` to map the input range of the tweener to the
118output values of the sound volume.
119
120Once the player goes underwater, we can smoothly transition the tweener's value from
121`0.0` to `1.0`, which will automatically change the drum volume and piano filter frequency.
122
123```no_run
124# use kira::{
125# 	AudioManager, AudioManagerSettings, DefaultBackend,
126# 	modulator::tweener::TweenerBuilder,
127# 	track::TrackBuilder,
128#   effect::filter::FilterBuilder,
129# 	sound::static_sound::StaticSoundData,
130# 	Mapping, Value, Easing,
131# 	Decibels,
132# };
133use kira::Tween;
134use std::time::Duration;
135
136# let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
137# let mut tweener = manager.add_modulator(TweenerBuilder { initial_value: 0.0 })?;
138# let filter_builder = FilterBuilder::new()
139# 	.cutoff(Value::from_modulator(&tweener, Mapping {
140# 		input_range: (0.0, 1.0),
141# 		output_range: (20_000.0, 500.0),
142# 		easing: Easing::Linear,
143# 	}));
144# let mut piano_track = manager.add_sub_track(TrackBuilder::new().with_effect(filter_builder))?;
145# piano_track.play(StaticSoundData::from_file("piano.ogg")?)?;
146# manager.play(
147# 	StaticSoundData::from_file("drums.ogg")?
148# 		.volume(Value::from_modulator(&tweener, Mapping {
149# 	 		input_range: (0.0, 1.0),
150# 	 		output_range: (Decibels::IDENTITY, Decibels::SILENCE),
151# 	 		easing: Easing::Linear,
152# 	 	}))
153# )?;
154tweener.set(1.0, Tween {
155	duration: Duration::from_secs(3),
156	..Default::default()
157});
158# Result::<(), Box<dyn std::error::Error>>::Ok(())
159```
160
161*/
162
163pub mod lfo;
164pub mod tweener;
165
166use atomic_arena::Key;
167
168use crate::info::Info;
169
170/// Configures a modulator.
171pub trait ModulatorBuilder {
172	/// Allows the user to control the modulator from gameplay code.
173	type Handle;
174
175	/// Creates the modulator and a handle to the modulator.
176	#[must_use]
177	fn build(self, id: ModulatorId) -> (Box<dyn Modulator>, Self::Handle);
178}
179
180/// Produces a stream of values that a parameter can be linked to.
181pub trait Modulator: Send {
182	/// Called whenever a new batch of audio samples is requested by the backend.
183	///
184	/// This is a good place to put code that needs to run fairly frequently,
185	/// but not for every single audio sample.
186	fn on_start_processing(&mut self) {}
187
188	/// Updates the modulator.
189	///
190	/// `dt` is the time that's elapsed since the previous round of
191	/// processing (in seconds).
192	fn update(&mut self, dt: f64, info: &Info);
193
194	/// Returns the current output of the modulator.
195	#[must_use]
196	fn value(&self) -> f64;
197
198	/// Whether the modulator can be removed from the audio context.
199	#[must_use]
200	fn finished(&self) -> bool;
201}
202
203/// A unique identifier for a modulator.
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
205pub struct ModulatorId(pub(crate) Key);