11//! Simple setup for a game: main bus, music and sfx channels
22//!
3- //! [Music sampler pool](Music)
4- //! [Sfx sampler pool](Sfx)
5- //!
6- //! ```text
7- //! ┌─────┐┌───┐┌───────────┐
8- //! │Music││Sfx││DefaultPool│
9- //! └┬────┘└┬──┘└┬──────────┘
10- //! ┌▽──────▽────▽┐
11- //! │MainBus │
12- //! └─────────────┘
13- //! ```
14- //!
153//! The `Music` pool, `Sfx` pool, and `DefaultPool` are all routed to the `MainBus` node.
164//! Since each pool has a `VolumeNode`, we can control them all individually. And,
175//! since they're all routed to the `MainBus`, we can also set the volume of all three
186//! at once.
197//!
208//! You can see this in action in the knob observers: to set the master volume,
21- //! we adjust the `MainBus ` node, and to set the individual volumes, we adjust the
9+ //! we adjust the `MainPool ` node, and to set the individual volumes, we adjust the
2210//! pool nodes.
2311//!
2412//! # Example
2513//! ```rust,no_run
14+ //! use bevy_seedling::{
15+ //! configuration::{MusicPool, SfxBus},
16+ //! pool::SamplerPool,
17+ //! prelude::*,
18+ //! };
2619//! #[derive(Resource, Debug, Clone, Serialize, Deserialize, Reflect)]
2720//! pub struct Sound {
2821//! pub general: f32,
2922//! pub music: f32,
3023//! pub sfx: f32,
3124//! }
25+ //!
3226//! fn lower_general(
3327//! mut sound: ResMut<Sound>,
3428//! mut general: Single<&mut VolumeNode, With<MainBus>>,
3731//! sound.general = new_volume;
3832//! general.volume = Volume::Linear(new_volume);
3933//! }
34+ //!
35+ //! fn play_music(
36+ //! _: Trigger<Pointer<Click>>,
37+ //! playing: Query<(), (With<MusicPool>, With<SamplePlayer>)>,
38+ //! mut commands: Commands,
39+ //! server: Res<AssetServer>,
40+ //! ) {
41+ //! // We'll only play music if it's not already playing.
42+ //! if playing.iter().len() > 0 {
43+ //! return;
44+ //! }
45+ //!
46+ //! commands.spawn((
47+ //! // Including the `MusicPool` marker queues this sample in the `MusicPool`.
48+ //! MusicPool,
49+ //! SamplePlayer::new(source).with_volume(Volume::Decibels(-6.0)),
50+ //! ));
51+ //! }
52+ //!
53+ //! fn play_sfx(_: Trigger<Pointer<Click>>, mut commands: Commands, server: Res<AssetServer>) {
54+ //! let source = server.load("caw.ogg");
55+ //! // The default pool is routed to the `SfxBus`, so we don't
56+ //! // need to include any special markers for sound effects.
57+ //! commands.spawn(SamplePlayer::new(source));
58+ //! }
4059//! ```
4160//!
4261use bevy:: prelude:: * ;
43- use bevy_seedling:: { pool:: SamplerPool , prelude:: * } ;
62+ use bevy_seedling:: prelude:: * ;
63+
64+ /// Utility for converting a simple `[0.0, 1.0]` range to [`Volume`].
65+ ///
66+ ///# Example
67+ /// ```
68+ /// use bevy_seedling::prelude::*;
69+ /// use bevy::prelude::*;
70+ ///
71+ /// const STEP: f32 = 0.1;
72+ /// const MIN_VOLUME: f32 = 0.0;
73+ /// const MAX_VOLUME: f32 = 1.0;
74+ ///
75+ /// pub fn increment_volume(volume: Volume) -> Volume {
76+ /// let perceptual = CONVERTER.volume_to_perceptual(volume);
77+ /// let new_perceptual = (perceptual + STEP).min(MAX_VOLUME);
78+ /// CONVERTER.perceptual_to_volume(new_perceptual)
79+ /// }
80+ /// ```
81+ pub const CONVERTER : PerceptualVolume =PerceptualVolume :: new ( ) ;
4482
4583pub fn plugin ( app : & mut App ) {
4684// #[cfg(target_arch = "wasm32")]
@@ -56,67 +94,9 @@ pub fn plugin(app: &mut App) {
5694// #[cfg(not(target_arch = "wasm32"))]
5795 app. add_plugins ( bevy_seedling:: SeedlingPlugin :: default ( ) ) ;
5896
59- app. add_systems ( Startup , spawn_pools ) ;
97+ app. add_systems ( Startup , setup ) ;
6098}
6199
62- fn spawn_pools ( mut master : Single < & mut VolumeNode , With < MainBus > > , mut cmds : Commands ) {
63- // Since the main bus already exists, we can just set the desired volume.
64- master. volume =Volume :: UNITY_GAIN ;
65-
66- cmds. spawn ( (
67- SamplerPool ( Music ) ,
68- VolumeNode {
69- volume : Volume :: Linear ( 0.5 ) ,
70- } ,
71- ) ) ;
72- cmds. spawn ( (
73- SamplerPool ( Sfx ) ,
74- VolumeNode {
75- volume : Volume :: Linear ( 0.5 ) ,
76- } ,
77- ) ) ;
100+ fn setup ( mut master : Single < & mut VolumeNode , With < MainBus > > ) {
101+ master. volume =CONVERTER . perceptual_to_volume ( 0.7 ) ;
78102}
79-
80- /// An organizational marker component that indicates that [`SamplePlayer`] should be routed to the music sampler pool.
81- ///
82- /// Suitable for anything in "music" category (e.g. global background music, soundtrack)
83- ///
84- /// This can then be used to query for and operate on sounds in that category
85- /// ```rust,no_run
86- /// commands.spawn(
87- /// Music,
88- /// SamplePlayer::new(handle).with_volume(Volume::Linear(vol)),
89- /// );
90- ///
91- /// // or looping
92- ///
93- /// commands.spawn(
94- /// Music,
95- /// SamplePlayer::new(handle).with_volume(Volume::Linear(vol)).looping(),
96- /// );
97- /// ```
98- #[ derive( PoolLabel , Debug , Clone , PartialEq , Eq , Hash , Default , Reflect ) ]
99- #[ reflect( Component ) ]
100- pub struct Music ;
101-
102- /// An organizational marker component that indicates that [`SamplePlayer`] should be routed to the SFX sampler pool.
103- ///
104- /// Suitable for anything in "sound effect" category (e.g. footsteps, the sound of a magic spell, a door opening)
105- ///
106- /// This can then be used to query for and operate on sounds in that category
107- /// ```rust,no_run
108- /// commands.spawn(
109- /// Sfx,
110- /// SamplePlayer::new(handle).with_volume(Volume::Linear(vol)),
111- /// );
112- ///
113- /// // or looping
114- ///
115- /// commands.spawn(
116- /// Sfx,
117- /// SamplePlayer::new(handle).with_volume(Volume::Linear(vol)).looping(),
118- /// );
119- /// ```
120- #[ derive( PoolLabel , Debug , Clone , PartialEq , Eq , Hash , Default , Reflect ) ]
121- #[ reflect( Component ) ]
122- pub struct Sfx ;