From 6af8349d9fdef0d73b3b2734cf2891d8589d5f3f Mon Sep 17 00:00:00 2001 From: Andrew Minnich Date: Sat, 14 Dec 2024 03:48:19 -0500 Subject: [PATCH] single mixer track with volume control --- crates/kira/src/decibels.rs | 118 ++++++++++++++++++ crates/kira/src/lib.rs | 3 + crates/kira/src/manager.rs | 42 +++++-- crates/kira/src/manager/settings.rs | 8 +- crates/kira/src/renderer.rs | 4 +- crates/kira/src/resources.rs | 22 ++-- crates/kira/src/resources/mixer.rs | 48 ++++++++ crates/kira/src/resources/sounds.rs | 38 ------ crates/kira/src/track.rs | 3 + crates/kira/src/track/main.rs | 56 +++++++++ crates/kira/src/track/main/builder.rs | 166 ++++++++++++++++++++++++++ crates/kira/src/track/main/handle.rs | 50 ++++++++ crates/kira/src/tween/parameter.rs | 9 +- crates/sandbox/src/main.rs | 10 +- 14 files changed, 514 insertions(+), 63 deletions(-) create mode 100644 crates/kira/src/decibels.rs create mode 100644 crates/kira/src/resources/mixer.rs delete mode 100644 crates/kira/src/resources/sounds.rs create mode 100644 crates/kira/src/track.rs create mode 100644 crates/kira/src/track/main.rs create mode 100644 crates/kira/src/track/main/builder.rs create mode 100644 crates/kira/src/track/main/handle.rs diff --git a/crates/kira/src/decibels.rs b/crates/kira/src/decibels.rs new file mode 100644 index 00000000..f9145aa6 --- /dev/null +++ b/crates/kira/src/decibels.rs @@ -0,0 +1,118 @@ +use std::ops::{Add, AddAssign, Sub, SubAssign}; + +use crate::{tween::Tweenable, Value}; + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +/// Represents a change in volume. +/// +/// Higher values increase the volume and lower values decrease it. +/// Setting the volume of a sound to -60dB or lower makes it silent. +pub struct Decibels(pub f32); + +impl Decibels { + /// The minimum decibel value at which a sound is considered + /// silent. + pub const SILENCE: Self = Self(-60.0); + /// The decibel value that produces no change in volume. + pub const IDENTITY: Self = Self(0.0); + + /// Converts decibels to amplitude, a linear volume measurement. + /// + /// This returns a number from `0.0`-`1.0` that you can multiply + /// a singal by to change its volume. + pub fn as_amplitude(self) -> f32 { + // adding a special case for db == 0.0 improves + // performance in the sound playback benchmarks + // by about 7% + if self == Self(0.0) { + return 1.0; + } + if self <= Self::SILENCE { + return 0.0; + } + 10.0f32.powf(self.0 / 20.0) + } +} + +impl Default for Decibels { + fn default() -> Self { + Self::IDENTITY + } +} + +impl Tweenable for Decibels { + fn interpolate(a: Self, b: Self, amount: f64) -> Self { + Self(Tweenable::interpolate(a.0, b.0, amount)) + } +} + +impl From for Decibels { + fn from(value: f32) -> Self { + Self(value) + } +} + +impl From for Value { + fn from(value: f32) -> Self { + Value::Fixed(Decibels(value)) + } +} + +impl From for Value { + fn from(value: Decibels) -> Self { + Value::Fixed(value) + } +} + +impl Add for Decibels { + type Output = Decibels; + + fn add(self, rhs: Decibels) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for Decibels { + fn add_assign(&mut self, rhs: Decibels) { + self.0 += rhs.0; + } +} + +impl Sub for Decibels { + type Output = Decibels; + + fn sub(self, rhs: Decibels) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl SubAssign for Decibels { + fn sub_assign(&mut self, rhs: Decibels) { + self.0 -= rhs.0; + } +} + +#[cfg(test)] +#[test] +#[allow(clippy::float_cmp)] +fn test() { + /// A table of dB values to the corresponding amplitudes. + // Data gathered from https://www.silisoftware.com/tools/db.php + const TEST_CALCULATIONS: [(Decibels, f32); 6] = [ + (Decibels::IDENTITY, 1.0), + (Decibels(3.0), 1.4125376), + (Decibels(12.0), 3.9810717), + (Decibels(-3.0), 0.70794576), + (Decibels(-12.0), 0.25118864), + (Decibels::SILENCE, 0.0), + ]; + + for (decibels, amplitude) in TEST_CALCULATIONS { + assert!((decibels.as_amplitude() - amplitude).abs() < 0.00001); + } + + // test some special cases + assert_eq!((Decibels::SILENCE - Decibels(100.0)).as_amplitude(), 0.0); +} diff --git a/crates/kira/src/lib.rs b/crates/kira/src/lib.rs index a3e4eb7f..a7972a81 100644 --- a/crates/kira/src/lib.rs +++ b/crates/kira/src/lib.rs @@ -13,6 +13,7 @@ mod arena; pub mod backend; pub mod clock; pub mod command; +mod decibels; mod error; mod frame; pub mod info; @@ -23,9 +24,11 @@ pub mod renderer; mod resources; pub mod sound; mod start_time; +pub mod track; mod tween; mod value; +pub use decibels::*; pub use error::*; pub use frame::*; pub use panning::*; diff --git a/crates/kira/src/manager.rs b/crates/kira/src/manager.rs index 2c2a4bd6..f466b70e 100644 --- a/crates/kira/src/manager.rs +++ b/crates/kira/src/manager.rs @@ -12,6 +12,7 @@ use crate::{ modulators::buffered_modulator::BufferedModulator, ResourceControllers, }, sound::SoundData, + track::MainTrackHandle, PlaySoundError, ResourceLimitReached, Value, }; @@ -23,7 +24,11 @@ pub struct AudioManager { impl AudioManager { pub fn new(settings: AudioManagerSettings) -> Result { let (mut backend, sample_rate) = B::setup(settings.backend_settings)?; - let (resources, resource_controllers) = create_resources(sample_rate, settings.capacities); + let (resources, resource_controllers) = create_resources( + sample_rate, + settings.capacities, + settings.main_track_builder, + ); let renderer = Renderer::new(sample_rate, resources); backend.start(renderer)?; Ok(Self { @@ -36,14 +41,7 @@ impl AudioManager { &mut self, sound_data: D, ) -> Result> { - let (sound, handle) = sound_data - .into_sound() - .map_err(PlaySoundError::IntoSoundError)?; - self.resource_controllers - .sound_controller - .insert(sound) - .map_err(|_| PlaySoundError::SoundLimitReached)?; - Ok(handle) + self.resource_controllers.main_track_handle.play(sound_data) } pub fn add_clock( @@ -75,6 +73,32 @@ impl AudioManager { Ok(handle) } + /** + Returns a handle to the main mixer track. + + # Examples + + Use the main track handle to adjust the volume of all audio: + + ```no_run + # use kira::{ + # manager::{ + # AudioManager, AudioManagerSettings, + # backend::DefaultBackend, + # }, + # }; + use kira::tween::Tween; + + # let mut manager = AudioManager::::new(AudioManagerSettings::default())?; + manager.main_track().set_volume(-6.0, Tween::default()); + # Result::<(), Box>::Ok(()) + ``` + */ + #[must_use] + pub fn main_track(&mut self) -> &mut MainTrackHandle { + &mut self.resource_controllers.main_track_handle + } + /// Returns a mutable reference to this manager's backend. #[must_use] pub fn backend_mut(&mut self) -> &mut B { diff --git a/crates/kira/src/manager/settings.rs b/crates/kira/src/manager/settings.rs index 9987dd94..d3e45996 100644 --- a/crates/kira/src/manager/settings.rs +++ b/crates/kira/src/manager/settings.rs @@ -1,12 +1,12 @@ -use crate::backend::Backend; +use crate::{backend::Backend, track::MainTrackBuilder}; /// Settings for an [`AudioManager`](super::AudioManager). pub struct AudioManagerSettings { /// Specifies how many of each resource type an audio context /// can have. pub capacities: Capacities, - /* /// Configures the main mixer track. - pub main_track_builder: MainTrackBuilder, */ + /// Configures the main mixer track. + pub main_track_builder: MainTrackBuilder, /// Configures the backend. pub backend_settings: B::Settings, } @@ -18,7 +18,7 @@ where fn default() -> Self { Self { capacities: Capacities::default(), - // main_track_builder: MainTrackBuilder::default(), + main_track_builder: MainTrackBuilder::default(), backend_settings: B::Settings::default(), } } diff --git a/crates/kira/src/renderer.rs b/crates/kira/src/renderer.rs index d50e0eaf..29fd4371 100644 --- a/crates/kira/src/renderer.rs +++ b/crates/kira/src/renderer.rs @@ -19,7 +19,7 @@ impl Renderer { pub fn on_start_processing(&mut self) { self.resources.clocks.on_start_processing(); - self.resources.sounds.on_start_processing(); + self.resources.mixer.on_start_processing(); self.resources.modulators.on_start_processing(); } @@ -47,7 +47,7 @@ impl Renderer { // process sounds in chunks let mut frames = [Frame::ZERO; INTERNAL_BUFFER_SIZE]; - self.resources.sounds.process( + self.resources.mixer.process( &mut frames[..num_frames], self.dt, &self.resources.clocks, diff --git a/crates/kira/src/resources.rs b/crates/kira/src/resources.rs index 0fd59b10..eb3448f1 100644 --- a/crates/kira/src/resources.rs +++ b/crates/kira/src/resources.rs @@ -1,6 +1,6 @@ pub(crate) mod clocks; +pub(crate) mod mixer; pub(crate) mod modulators; -pub(crate) mod sounds; use std::{ fmt::{Debug, Formatter}, @@ -8,32 +8,38 @@ use std::{ }; use clocks::{buffered_clock::BufferedClock, Clocks}; +use mixer::Mixer; use modulators::{buffered_modulator::BufferedModulator, Modulators}; use rtrb::{Consumer, Producer, RingBuffer}; -use sounds::Sounds; use crate::{ arena::{Arena, Controller, Key}, manager::Capacities, - sound::Sound, + track::{MainTrackBuilder, MainTrackHandle}, ResourceLimitReached, }; pub(crate) fn create_resources( sample_rate: u32, capacities: Capacities, + main_track_builder: MainTrackBuilder, ) -> (Resources, ResourceControllers) { - let (sounds, sound_controller) = Sounds::new(5000); + let (mixer, /* sub_track_controller, send_track_controller, */ main_track_handle) = Mixer::new( + // capacities.sub_track_capacity, + // capacities.send_track_capacity, + // sample_rate, + main_track_builder, + ); let (clocks, clock_controller) = Clocks::new(capacities.clock_capacity); let (modulators, modulator_controller) = Modulators::new(capacities.modulator_capacity); ( Resources { - sounds, + mixer, clocks, modulators, }, ResourceControllers { - sound_controller, + main_track_handle, clock_controller, modulator_controller, }, @@ -41,13 +47,13 @@ pub(crate) fn create_resources( } pub(crate) struct Resources { - pub sounds: Sounds, + pub mixer: Mixer, pub clocks: Clocks, pub modulators: Modulators, } pub(crate) struct ResourceControllers { - pub sound_controller: ResourceController>, + pub main_track_handle: MainTrackHandle, pub clock_controller: ResourceController, pub modulator_controller: ResourceController, } diff --git a/crates/kira/src/resources/mixer.rs b/crates/kira/src/resources/mixer.rs new file mode 100644 index 00000000..d7a89e0b --- /dev/null +++ b/crates/kira/src/resources/mixer.rs @@ -0,0 +1,48 @@ +use crate::{ + track::{MainTrack, MainTrackBuilder, MainTrackHandle}, + Frame, +}; + +use super::{Clocks, Modulators}; + +pub struct Mixer { + main_track: MainTrack, +} + +impl Mixer { + pub fn new( + main_track_builder: MainTrackBuilder, + ) -> ( + Self, + // ResourceController, + // ResourceController, + MainTrackHandle, + ) { + let (main_track, main_track_handle) = main_track_builder.build(); + (Self { main_track }, main_track_handle) + } + + pub fn on_start_processing(&mut self) { + /* self.sub_tracks + .remove_and_add(|track| track.should_be_removed()); + for (_, track) in &mut self.sub_tracks { + track.on_start_processing(); + } + self.send_tracks + .remove_and_add(|track| track.shared().is_marked_for_removal()); + for (_, track) in &mut self.send_tracks { + track.on_start_processing(); + } */ + self.main_track.on_start_processing(); + } + + pub(crate) fn process( + &mut self, + out: &mut [Frame], + dt: f64, + clocks: &Clocks, + modulators: &Modulators, + ) { + self.main_track.process(out, dt, clocks, modulators) + } +} diff --git a/crates/kira/src/resources/sounds.rs b/crates/kira/src/resources/sounds.rs deleted file mode 100644 index 6c2176e6..00000000 --- a/crates/kira/src/resources/sounds.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::{info::Info, sound::Sound, Frame, INTERNAL_BUFFER_SIZE}; - -use super::{Clocks, Modulators, ResourceController, ResourceStorage}; - -pub(crate) struct Sounds(pub(crate) ResourceStorage>); - -impl Sounds { - #[must_use] - pub(crate) fn new(capacity: u16) -> (Self, ResourceController>) { - let (storage, controller) = ResourceStorage::new(capacity); - (Self(storage), controller) - } - - pub(crate) fn on_start_processing(&mut self) { - self.0.remove_and_add(|clock| clock.finished()); - for (_, clock) in &mut self.0 { - clock.on_start_processing(); - } - } - - pub(crate) fn process( - &mut self, - out: &mut [Frame], - dt: f64, - clocks: &Clocks, - modulators: &Modulators, - ) { - let mut per_sound_buffer = [Frame::ZERO; INTERNAL_BUFFER_SIZE]; - let info = Info::new(&clocks.0.resources, &modulators.0.resources); - for (_, sound) in &mut self.0 { - sound.process(&mut per_sound_buffer[..out.len()], dt, &info); - for (summed_out, sound_out) in out.iter_mut().zip(per_sound_buffer.iter_mut()) { - *summed_out += *sound_out; - } - per_sound_buffer = [Frame::ZERO; INTERNAL_BUFFER_SIZE]; - } - } -} diff --git a/crates/kira/src/track.rs b/crates/kira/src/track.rs new file mode 100644 index 00000000..ed2c7361 --- /dev/null +++ b/crates/kira/src/track.rs @@ -0,0 +1,3 @@ +mod main; + +pub use main::*; diff --git a/crates/kira/src/track/main.rs b/crates/kira/src/track/main.rs new file mode 100644 index 00000000..2f540e85 --- /dev/null +++ b/crates/kira/src/track/main.rs @@ -0,0 +1,56 @@ +mod builder; +mod handle; + +pub use builder::*; +pub use handle::*; + +use crate::{ + command::{CommandReader, ValueChangeCommand}, + info::Info, + resources::{clocks::Clocks, modulators::Modulators, ResourceStorage}, + sound::Sound, + Decibels, Frame, Parameter, INTERNAL_BUFFER_SIZE, +}; + +pub(crate) struct MainTrack { + volume: Parameter, + set_volume_command_reader: CommandReader>, + sounds: ResourceStorage>, +} + +impl MainTrack { + pub fn on_start_processing(&mut self) { + self.volume + .read_command(&mut self.set_volume_command_reader); + self.sounds.remove_and_add(|sound| sound.finished()); + for (_, sound) in &mut self.sounds { + sound.on_start_processing(); + } + /* for effect in &mut self.effects { + effect.on_start_processing(); + } */ + } + + pub(crate) fn process( + &mut self, + out: &mut [Frame], + dt: f64, + clocks: &Clocks, + modulators: &Modulators, + ) { + let mut per_sound_buffer = [Frame::ZERO; INTERNAL_BUFFER_SIZE]; + let info = Info::new(&clocks.0.resources, &modulators.0.resources); + for (_, sound) in &mut self.sounds { + sound.process(&mut per_sound_buffer[..out.len()], dt, &info); + for (summed_out, sound_out) in out.iter_mut().zip(per_sound_buffer.iter_mut()) { + *summed_out += *sound_out; + } + per_sound_buffer = [Frame::ZERO; INTERNAL_BUFFER_SIZE]; + } + let mut volume_buffer = [Decibels::IDENTITY; INTERNAL_BUFFER_SIZE]; + self.volume.update_chunk(&mut volume_buffer, dt, &info); + for (frame, volume) in out.iter_mut().zip(volume_buffer.iter().copied()) { + *frame *= volume.as_amplitude(); + } + } +} diff --git a/crates/kira/src/track/main/builder.rs b/crates/kira/src/track/main/builder.rs new file mode 100644 index 00000000..d5a0af4c --- /dev/null +++ b/crates/kira/src/track/main/builder.rs @@ -0,0 +1,166 @@ +use crate::{ + command::command_writer_and_reader, resources::ResourceStorage, tween::Parameter, Decibels, + Value, +}; + +use super::{MainTrack, MainTrackHandle}; + +/// Configures the main mixer track. +pub struct MainTrackBuilder { + /// The volume of the track. + pub(crate) volume: Value, + /* /// The effects that should be applied to the input audio + /// for this track. + pub(crate) effects: Vec>, */ + /// The maximum number of sounds that can be played simultaneously on this track. + pub(crate) sound_capacity: u16, +} + +impl MainTrackBuilder { + /// Creates a new [`MainTrackBuilder`] with the default settings. + #[must_use] + pub fn new() -> Self { + Self { + volume: Value::Fixed(Decibels::IDENTITY), + // effects: vec![], + sound_capacity: 128, + } + } + + /// Sets the volume of the main mixer track. + #[must_use = "This method consumes self and returns a modified MainTrackBuilder, so the return value should be used"] + pub fn volume(self, volume: impl Into>) -> Self { + Self { + volume: volume.into(), + ..self + } + } + + /// Sets the maximum number of sounds that can be played simultaneously on this track. + #[must_use = "This method consumes self and returns a modified MainTrackBuilder, so the return value should be used"] + pub fn sound_capacity(self, capacity: u16) -> Self { + Self { + sound_capacity: capacity, + ..self + } + } + + /* /** + Adds an effect to the track. + + # Examples + + ``` + use kira::{track::MainTrackBuilder, effect::delay::DelayBuilder}; + + let mut builder = MainTrackBuilder::new(); + let delay_handle = builder.add_effect(DelayBuilder::new()); + ``` + */ + pub fn add_effect(&mut self, builder: B) -> B::Handle { + let (effect, handle) = builder.build(); + self.effects.push(effect); + handle + } + + /* /** + Adds an effect to the track and returns the [`MainTrackBuilder`]. + + If you need to modify the effect later, use [`add_effect`](Self::add_effect), + which returns the effect handle. + + # Examples + + ``` + use kira::{ + track::MainTrackBuilder, + effect::{filter::FilterBuilder, reverb::ReverbBuilder}, + }; + + let mut builder = MainTrackBuilder::new() + .with_effect(FilterBuilder::new()) + .with_effect(ReverbBuilder::new()); + ``` + */ + #[must_use = "This method consumes self and returns a modified MainTrackBuilder, so the return value should be used"] + pub fn with_effect(mut self, builder: B) -> Self { + self.add_effect(builder); + self + } */ + + /** Adds an already built effect into this track. + + `Box` values are created when calling `build` on an effect builder, which gives you + an effect handle, as well as this boxed effect, which is the actual audio effect. + + This is a lower-level method than [`Self::add_effect`], and you should probably use it rather + than this method, unless you have a reason to. + + # Examples + + ``` + use kira::track::MainTrackBuilder; + use kira::effect::{EffectBuilder, delay::DelayBuilder}; + + let mut builder = MainTrackBuilder::new(); + let delay_builder = DelayBuilder::new(); + let (effect, delay_handle) = delay_builder.build(); + let delay_handle = builder.add_built_effect(effect); + ``` + */ + pub fn add_built_effect(&mut self, effect: Box) { + self.effects.push(effect); + } */ + + /* /** Add an already-built effect and return the [`MainTrackBuilder`]. + + `Box` values are created when calling `build` on an effect builder, which gives you + an effect handle, as well as this boxed effect, which is the actual audio effect. + + This is a lower-level method than [`Self::with_effect`], and you should probably use it rather + than this method, unless you have a reason to. + + # Examples + + ``` + use kira::{ + track::MainTrackBuilder, + effect::{filter::FilterBuilder, reverb::ReverbBuilder, EffectBuilder}, + }; + + let (filter_effect, filter_handle) = FilterBuilder::new().build(); + let (reverb_effect, reverb_handle) = ReverbBuilder::new().build(); + let mut builder = MainTrackBuilder::new() + .with_built_effect(filter_effect) + .with_built_effect(reverb_effect); + ``` + */ + #[must_use = "This method consumes self and returns a modified MainTrackBuilder, so the return value should be used"] + pub fn with_built_effect(mut self, effect: Box) -> Self { + self.add_built_effect(effect); + self + } */ + + #[must_use] + pub(crate) fn build(self) -> (MainTrack, MainTrackHandle) { + let (set_volume_command_writer, set_volume_command_reader) = command_writer_and_reader(); + let (sounds, sound_controller) = ResourceStorage::new(self.sound_capacity); + let track = MainTrack { + volume: Parameter::new(self.volume, Decibels::IDENTITY), + set_volume_command_reader, + sounds, + // effects: self.effects, + }; + let handle = MainTrackHandle { + set_volume_command_writer, + sound_controller, + }; + (track, handle) + } +} + +impl Default for MainTrackBuilder { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/kira/src/track/main/handle.rs b/crates/kira/src/track/main/handle.rs new file mode 100644 index 00000000..7bf07df6 --- /dev/null +++ b/crates/kira/src/track/main/handle.rs @@ -0,0 +1,50 @@ +use crate::{ + command::{CommandWriter, ValueChangeCommand}, + resources::ResourceController, + sound::{Sound, SoundData}, + tween::Tween, + Decibels, PlaySoundError, Value, +}; + +/// Controls the main mixer track. +#[derive(Debug)] +pub struct MainTrackHandle { + pub(crate) set_volume_command_writer: CommandWriter>, + pub(crate) sound_controller: ResourceController>, +} + +impl MainTrackHandle { + /// Plays a sound. + pub fn play( + &mut self, + sound_data: D, + ) -> Result> { + let (sound, handle) = sound_data + .into_sound() + .map_err(PlaySoundError::IntoSoundError)?; + self.sound_controller + .insert(sound) + .map_err(|_| PlaySoundError::SoundLimitReached)?; + Ok(handle) + } + + /// Sets the (post-effects) volume of the mixer track. + pub fn set_volume(&mut self, volume: impl Into>, tween: Tween) { + self.set_volume_command_writer.write(ValueChangeCommand { + target: volume.into(), + tween, + }) + } + + /// Returns the maximum number of sounds that can play simultaneously on this track. + #[must_use] + pub fn sound_capacity(&self) -> u16 { + self.sound_controller.capacity() + } + + /// Returns the number of sounds currently playing on this track. + #[must_use] + pub fn num_sounds(&self) -> u16 { + self.sound_controller.len() + } +} diff --git a/crates/kira/src/tween/parameter.rs b/crates/kira/src/tween/parameter.rs index 13fc5051..1ce505bf 100644 --- a/crates/kira/src/tween/parameter.rs +++ b/crates/kira/src/tween/parameter.rs @@ -5,7 +5,7 @@ use std::time::Duration; use crate::{ command::{CommandReader, ValueChangeCommand}, - info::{SingleFrameInfo, WhenToStart}, + info::{Info, SingleFrameInfo, WhenToStart}, tween::{Tween, Tweenable}, StartTime, Value, }; @@ -89,6 +89,13 @@ impl Parameter { just_finished_tween } + pub fn update_chunk(&mut self, out: &mut [T], dt: f64, info: &Info) { + for (i, value) in out.iter_mut().enumerate() { + self.update(dt, &info.for_single_frame(i)); + *value = self.value(); + } + } + fn update_tween(&mut self, dt: f64, info: &SingleFrameInfo) -> JustFinishedTween { if let State::Tweening { target, diff --git a/crates/sandbox/src/main.rs b/crates/sandbox/src/main.rs index 00322907..009867e2 100644 --- a/crates/sandbox/src/main.rs +++ b/crates/sandbox/src/main.rs @@ -6,7 +6,7 @@ use kira::{ manager::{AudioManager, AudioManagerSettings}, modulator::tweener::TweenerBuilder, sound::sine::SineBuilder, - Easing, Mapping, StartTime, Tween, Value, + Decibels, Easing, Mapping, StartTime, Tween, Value, }; fn main() -> Result<(), Box> { @@ -38,6 +38,14 @@ fn main() -> Result<(), Box> { })?; } clock.start(); + manager.main_track().set_volume( + Decibels::SILENCE, + Tween { + duration: Duration::from_secs(5), + start_time: StartTime::ClockTime(clock.time() + 4), + ..Default::default() + }, + ); loop { println!("{:?}", clock.time());