Skip to content

Commit

Permalink
examples: add custom_sound.rs example
Browse files Browse the repository at this point in the history
  • Loading branch information
SolarLiner committed May 6, 2024
1 parent 0b68f95 commit 25656ed
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 170 deletions.
13 changes: 2 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 31 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,37 @@ default-features = false
features = ["bevy_asset"]

[dev-dependencies]
bevy = "0.13"
ringbuf = "0.3.3"

[dev-dependencies.bevy]
version = "0.13.0"
default-features = false
features = [
# Copied from bevy with "bevy_audio" removed
"animation",
"bevy_asset",
"bevy_gilrs",
"bevy_scene",
"bevy_winit",
"bevy_core_pipeline",
"bevy_pbr",
"bevy_gltf",
"bevy_render",
"bevy_sprite",
"bevy_text",
"bevy_ui",
"multi-threaded",
"png",
"hdr",
"vorbis",
"x11",
"bevy_gizmos",
"android_shared_stdcxx",
"tonemapping_luts",
"default_font",
"webgl2",
"bevy_debug_stepping",
]

[features]
default = []
Expand Down
106 changes: 57 additions & 49 deletions examples/custom_sound/src/sine_wave.rs → examples/custom_sound.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
use std::{convert::Infallible, f32::consts::TAU};
use std::convert::Infallible;
use std::f32::consts::TAU;

use bevy::prelude::*;
use bevy_kira_components::sources::AudioSource;
use bevy_kira_components::{
kira::{
self,
manager::error::PlaySoundError,
sound::{Sound, SoundData},
tween::{Parameter, Tween, Value},
},
prelude::*,
sources::AudioSourcePlugin,
};
use kira::manager::error::PlaySoundError;
use kira::sound::{Sound, SoundData};
use kira::tween::{Parameter, Tween, Value};
use ringbuf::{HeapConsumer, HeapProducer, HeapRb};

pub struct SineWavePlugin;
use bevy_kira_components::prelude::*;

fn main() {
App::new()
.add_plugins((
DefaultPlugins,
AudioPlugin,
AudioSourcePlugin::<SineAudio>::default(),
))
.add_systems(Startup, setup)
.run();
}

impl Plugin for SineWavePlugin {
fn build(&self, app: &mut bevy::prelude::App) {
app.add_plugins(AudioSourcePlugin::<SineWave>::default());
}
fn setup(mut commands: Commands, mut assets: ResMut<Assets<SineAudio>>) {
use bevy_kira_components::sources::AudioBundle;
let handle = assets.add(SineAudio);
commands.spawn(AudioBundle {
source: handle,
settings: SineAudioSettings { frequency: 440.0 },
..default()
});
}

/// Enum for commands the Handle (controlled within Bevy systems) can send to the sound (in the
Expand All @@ -33,42 +41,42 @@ impl Plugin for SineWavePlugin {
/// In this particular case we are using message passing, sending commands from other threads to
/// this one by way of a command enum (this one), sending them in a ring-buffer so that neither the
/// audio thread nor the sending thread has to wait on each other.
enum SineWaveCommand {
enum SineAudioCommand {
/// Set the frequency to a new value. It will use the provided `Tween` to transition from the
/// old value to this one.
/// old value to t:his one.
SetFrequency(Value<f32>, Tween),
}

/// Implementation of [`kira::sound::Sound`] that generates a sine wave at the given frequency.
struct SineWaveSound {
/// Implementation of [`Sound`] that generates a sine wave at the given frequency.
struct SineAudioSound {
/// Output destination. This tells kira where to route the audio data that the output of our
/// `Sound` implementation
output: kira::OutputDestination,
/// Commands receiver (aka. a consumer) of commands sent from other threads
commands: HeapConsumer<SineWaveCommand>,
/// Sine wave frequency (in Hz). Reuses `kira`'s [`kira::tween::Parameter`] struct to provide
commands: HeapConsumer<SineAudioCommand>,
/// Sine wave frequency (in Hz). Reuses `kira`'s [`Parameter`] struct to provide
/// click-free changes and ability to provide modulations.
frequency: Parameter<f32>,
/// Internal phase of the sine wave. We keep track of the phase instead of the time, as this
/// allows to modulate the frequency without glitches.
phase: f32,
}

impl Sound for SineWaveSound {
fn output_destination(&mut self) -> bevy_kira_components::kira::OutputDestination {
impl Sound for SineAudioSound {
fn output_destination(&mut self) -> kira::OutputDestination {
self.output
}

fn process(
&mut self,
dt: f64,
clock_info_provider: &bevy_kira_components::kira::clock::clock_info::ClockInfoProvider,
modulator_value_provider: &bevy_kira_components::kira::modulator::value_provider::ModulatorValueProvider,
) -> bevy_kira_components::kira::dsp::Frame {
clock_info_provider: &kira::clock::clock_info::ClockInfoProvider,
modulator_value_provider: &kira::modulator::value_provider::ModulatorValueProvider,
) -> kira::dsp::Frame {
// Receive and perform commands
while let Some(command) = self.commands.pop() {
match command {
SineWaveCommand::SetFrequency(freq, tween) => self.frequency.set(freq, tween),
SineAudioCommand::SetFrequency(freq, tween) => self.frequency.set(freq, tween),
}
}

Expand All @@ -80,7 +88,7 @@ impl Sound for SineWaveSound {
if self.phase > 1. {
self.phase -= 1.;
}
// 24 dB reduction to not blast the user's speakers (and ears)
// 24 dB = 8x reduction to not blast the user's speakers (and ears)
let sample = 0.125 * f32::sin(TAU * self.phase);

// Return the new stereo sample
Expand All @@ -95,10 +103,10 @@ impl Sound for SineWaveSound {
}
}

impl SineWaveSound {
/// Create a new [`SineWaveSound`] with the provided command buffer and frequency
impl SineAudioSound {
/// Create a new [`SineAudioSound`] with the provided command buffer and frequency
fn new(
commands: HeapConsumer<SineWaveCommand>,
commands: HeapConsumer<SineAudioCommand>,
output: kira::OutputDestination,
initial_frequency: f32,
) -> Self {
Expand All @@ -112,72 +120,72 @@ impl SineWaveSound {
}

/// Handle for sine wave sounds. Allows setting the frequency.
pub struct SineWaveHandle {
commands: HeapProducer<SineWaveCommand>,
pub struct SineAudioHandle {
commands: HeapProducer<SineAudioCommand>,
}

impl SineWaveHandle {
impl SineAudioHandle {
pub fn set_frequency(&mut self, frequency: impl Into<Value<f32>>, tween: Tween) {
if self.commands.is_full() {
error!("Cannot send command: command queue is full");
error!("maximum number of in-flight commands reached, cannot add any more");
return;
}
assert!(self
.commands
.push(SineWaveCommand::SetFrequency(frequency.into(), tween))
.push(SineAudioCommand::SetFrequency(frequency.into(), tween))
.is_ok());
}
}

/// Data and settings for the sine wave sound.
#[derive(Debug, Copy, Clone, Asset, TypePath)]
struct SineWaveData {
struct SineAudioData {
/// Output destination, for consumption by the sound in `kira`
output_destination: kira::OutputDestination,
/// Initial frequency of the sine wave at creation
intial_frequency: f32,
}

impl SoundData for SineWaveData {
impl SoundData for SineAudioData {
type Error = Infallible;

type Handle = SineWaveHandle;
type Handle = SineAudioHandle;

fn into_sound(self) -> Result<(Box<dyn Sound>, Self::Handle), Self::Error> {
let (producer, consumer) = HeapRb::new(16).split();
let sound = Box::new(SineWaveSound::new(
let sound = Box::new(SineAudioSound::new(
consumer,
self.output_destination,
self.intial_frequency,
));
let handle = SineWaveHandle { commands: producer };
let handle = SineAudioHandle { commands: producer };
Ok((sound, handle))
}
}

#[derive(Debug, Default, Component)]
pub struct SineWaveSettings {
pub struct SineAudioSettings {
pub frequency: f32,
}

/// Bevy asset for sine waves. Contains no data as the frequency is provided as a setting instead.
#[derive(Debug, Clone, Copy, Default, Asset, TypePath)]
pub struct SineWave;
pub struct SineAudio;

impl AudioSource for SineWave {
impl bevy_kira_components::prelude::AudioSource for SineAudio {
type Error = PlaySoundError<Infallible>;

type Handle = SineWaveHandle;
type Handle = SineAudioHandle;

type Settings = SineWaveSettings;
type Settings = SineAudioSettings;

fn create_handle(
&self,
manager: &mut kira::manager::AudioManager<AudioBackend>,
settings: &Self::Settings,
output_destination: kira::OutputDestination,
) -> Result<Self::Handle, Self::Error> {
manager.play(SineWaveData {
manager.play(SineAudioData {
intial_frequency: settings.frequency,
output_destination,
})
Expand Down
47 changes: 0 additions & 47 deletions examples/custom_sound/Cargo.toml

This file was deleted.

Loading

0 comments on commit 25656ed

Please sign in to comment.