Skip to content

Commit

Permalink
single mixer track with volume control
Browse files Browse the repository at this point in the history
  • Loading branch information
tesselode committed Dec 14, 2024
1 parent f755330 commit 6af8349
Show file tree
Hide file tree
Showing 14 changed files with 514 additions and 63 deletions.
118 changes: 118 additions & 0 deletions crates/kira/src/decibels.rs
Original file line number Diff line number Diff line change
@@ -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<f32> for Decibels {
fn from(value: f32) -> Self {
Self(value)
}
}

impl From<f32> for Value<Decibels> {
fn from(value: f32) -> Self {
Value::Fixed(Decibels(value))
}
}

impl From<Decibels> for Value<Decibels> {
fn from(value: Decibels) -> Self {
Value::Fixed(value)
}
}

impl Add<Decibels> for Decibels {
type Output = Decibels;

fn add(self, rhs: Decibels) -> Self::Output {
Self(self.0 + rhs.0)
}
}

impl AddAssign<Decibels> for Decibels {
fn add_assign(&mut self, rhs: Decibels) {
self.0 += rhs.0;
}
}

impl Sub<Decibels> for Decibels {
type Output = Decibels;

fn sub(self, rhs: Decibels) -> Self::Output {
Self(self.0 - rhs.0)
}
}

impl SubAssign<Decibels> 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);
}
3 changes: 3 additions & 0 deletions crates/kira/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod arena;
pub mod backend;
pub mod clock;
pub mod command;
mod decibels;
mod error;
mod frame;
pub mod info;
Expand All @@ -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::*;
Expand Down
42 changes: 33 additions & 9 deletions crates/kira/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
modulators::buffered_modulator::BufferedModulator, ResourceControllers,
},
sound::SoundData,
track::MainTrackHandle,
PlaySoundError, ResourceLimitReached, Value,
};

Expand All @@ -23,7 +24,11 @@ pub struct AudioManager<B: Backend = DefaultBackend> {
impl<B: Backend> AudioManager<B> {
pub fn new(settings: AudioManagerSettings<B>) -> Result<Self, B::Error> {
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 {
Expand All @@ -36,14 +41,7 @@ impl<B: Backend> AudioManager<B> {
&mut self,
sound_data: D,
) -> Result<D::Handle, PlaySoundError<D::Error>> {
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(
Expand Down Expand Up @@ -75,6 +73,32 @@ impl<B: Backend> AudioManager<B> {
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::<DefaultBackend>::new(AudioManagerSettings::default())?;
manager.main_track().set_volume(-6.0, Tween::default());
# Result::<(), Box<dyn std::error::Error>>::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 {
Expand Down
8 changes: 4 additions & 4 deletions crates/kira/src/manager/settings.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::backend::Backend;
use crate::{backend::Backend, track::MainTrackBuilder};

/// Settings for an [`AudioManager`](super::AudioManager).
pub struct AudioManagerSettings<B: Backend> {
/// 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,
}
Expand All @@ -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(),
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/kira/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -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,
Expand Down
22 changes: 14 additions & 8 deletions crates/kira/src/resources.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,59 @@
pub(crate) mod clocks;
pub(crate) mod mixer;
pub(crate) mod modulators;
pub(crate) mod sounds;

use std::{
fmt::{Debug, Formatter},
sync::Mutex,
};

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,

Check warning on line 23 in crates/kira/src/resources.rs

View workflow job for this annotation

GitHub Actions / desktop - no features

unused variable: `sample_rate`

warning: unused variable: `sample_rate` --> crates/kira/src/resources.rs:23:2 | 23 | sample_rate: u32, | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_sample_rate` | = note: `#[warn(unused_variables)]` on by default
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,
},
)
}

pub(crate) struct Resources {
pub sounds: Sounds,
pub mixer: Mixer,
pub clocks: Clocks,
pub modulators: Modulators,
}

pub(crate) struct ResourceControllers {
pub sound_controller: ResourceController<Box<dyn Sound>>,
pub main_track_handle: MainTrackHandle,
pub clock_controller: ResourceController<BufferedClock>,
pub modulator_controller: ResourceController<BufferedModulator>,
}
Expand Down
48 changes: 48 additions & 0 deletions crates/kira/src/resources/mixer.rs
Original file line number Diff line number Diff line change
@@ -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<Track>,
// ResourceController<SendTrack>,
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)
}
}
Loading

0 comments on commit 6af8349

Please sign in to comment.