1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
//! Combines individual sounds into larger pieces. //! //! `Arrangement`s are containers of `SoundClip`s, which are portions of //! a sound that can be positioned in time, stretched, trimmed, and //! reversed. You can play instances of an arrangement just like you would //! play instances of a sound. //! //! `Arrangement`s are a lot like arrangement views in DAWs, like the //! playlist view in FL Studio. In fact, the playlist view in FL Studio //! will be used to illustrate the contents of `Arrangement`s in the //! following examples. //! //! This image represents an arrangement that plays the same sound //! four times: once normally, once trimmed, once stretched out, //! and once reversed. //! //! ![arrangements 1](https://i.imgur.com/1p4W1Ld.png) //! //! ## Motivating example: seamless loops //! //! Oftentimes, game composers need to write pieces that loop forever. //! These pieces may also have an intro section that plays once //! before the main part of the song loops forever. `Instance`s allow //! you to set a loop start point so when the playback position reaches //! the end, it jumps back to an arbitrary point in the sound. //! //! The problem is this doesn't account for parts of the sound that //! bleed into the next section. For example, at the end of an orchestral //! piece, there may be a cymbal swell that transitions the song back //! to the beginning of the loop. To preserve the musical timing of the //! piece, the playback position needs to jump back to the start point //! as soon as the last measure of music ends, which would cut off //! the cymbal, creating a jarring sound. If the song has an intro section //! with trailing sound, then that sound will cut in when the song //! loops, which is also jarring. //! //! There's a couple possible solutions: //! - Use a [`Sequence`](crate::sequence) to play separate //! intro and loop sounds at the right time. This works, but you //! can't reverse or change the playback rate of a sequence, which you may //! want in some circumstances. //! - You can edit your intro and loop audio in a specific way to create a //! larger piece that will seamlessly loop. This is tedious, and you have //! to store a larger audio as part of the game's assets. //! //! Arrangements let you use the latter solution without having to store //! a larger audio file, and as you'll see, they can do the work of setting //! up seamless loops for you. //! //! ### Setting up a simple loop //! //! Let's say we have a short drum loop with a cymbal swell at the end //! that will seamless lead back to the beginning of the loop. //! //! ![arrangements 2](https://i.imgur.com/TOpa9Zq.png) //! //! We can set up a seamless loop by placing the same sound in an arrangement //! twice, once with the cymbal swell preserved and once with it cut off. //! The red region at the top shows the portion of the arrangement //! that will be looped. //! //! ![arrangements 3](https://i.imgur.com/Xoti30y.png) //! //! When the playback position jumps back to the loop point, the trailing sound //! from the first sound clip will seamlessly connect to the trailing //! sound that was cut off from the second clip. //! //! You can set up this arrangement manually: //! //! ```no_run //! # use kira::{ //! # arrangement::{Arrangement, ArrangementSettings, SoundClip}, //! # manager::{AudioManager, AudioManagerSettings}, //! # sound::{Sound, SoundSettings}, Tempo, //! # }; //! # //! # let mut audio_manager = AudioManager::new(AudioManagerSettings::default())?; //! # let sound_handle = audio_manager.add_sound(Sound::from_file( //! # std::env::current_dir()?.join("assets/loop.wav"), //! # SoundSettings::default(), //! # )?)?; //! # //! let tempo = Tempo(140.0); //! let mut arrangement = Arrangement::new( //! ArrangementSettings::new().default_loop_start(tempo.beats_to_seconds(16.0)), //! ); //! arrangement //! .add_clip(SoundClip::new(&sound_handle, 0.0)) //! .add_clip( //! SoundClip::new(&sound_handle, tempo.beats_to_seconds(16.0)) //! .trim(tempo.beats_to_seconds(16.0)), //! ); //! # Ok::<(), Box<dyn std::error::Error>>(()) //! ``` //! //! Or you can just use [`Arrangement::new_loop`], which will do the work for you: //! //! ```no_run //! # use kira::{ //! # arrangement::{Arrangement, LoopArrangementSettings, SoundClip}, //! # manager::{AudioManager, AudioManagerSettings}, //! # sound::{Sound, SoundSettings}, Tempo, //! # }; //! # //! # let mut audio_manager = AudioManager::new(AudioManagerSettings::default())?; //! let tempo = Tempo(140.0); //! let sound_handle = audio_manager.add_sound(Sound::from_file( //! std::env::current_dir()?.join("assets/loop.wav"), //! SoundSettings { //! semantic_duration: Some(tempo.beats_to_seconds(16.0)), //! ..Default::default() //! }, //! )?)?; //! let arrangement = Arrangement::new_loop(&sound_handle, LoopArrangementSettings::default()); //! # Ok::<(), Box<dyn std::error::Error>>(()) //! ``` //! //! ### Loops with intros //! //! Loops with intros can be set up in a similar way: //! //! ![arrangements 4](https://i.imgur.com/EM7P7Ry.png) //! //! For brevity, we'll just say you can use [`Arrangement::new_loop_with_intro`] //! to create these. mod clip; pub mod handle; mod id; mod settings; use basedrop::Owned; pub use clip::SoundClip; use handle::ArrangementHandle; pub use id::ArrangementId; pub use settings::{ArrangementSettings, LoopArrangementSettings}; use crate::{ group::{groups::Groups, GroupId, GroupSet}, mixer::TrackIndex, sound::{handle::SoundHandle, Sound, SoundId}, static_container::index_map::StaticIndexMap, Frame, }; /// An arrangement of sound clips to play at specific times. #[derive(Debug, Clone)] #[cfg_attr( feature = "serde_support", derive(serde::Serialize, serde::Deserialize) )] pub struct Arrangement { id: ArrangementId, clips: Vec<SoundClip>, duration: f64, default_track: TrackIndex, cooldown: Option<f64>, semantic_duration: Option<f64>, default_loop_start: Option<f64>, groups: GroupSet, cooldown_timer: f64, } impl Arrangement { /// Creates a new, empty arrangement. pub fn new(settings: ArrangementSettings) -> Self { Self { id: settings.id.unwrap_or(ArrangementId::new()), clips: vec![], duration: 0.0, default_track: settings.default_track, cooldown: settings.cooldown, semantic_duration: settings.semantic_duration, default_loop_start: settings.default_loop_start, groups: settings.groups, cooldown_timer: 0.0, } } /// Creates a new arrangement that seamlessly loops a sound. /// /// If the sound has a semantic duration, it will be used to /// set the point where the sound loops. Any audio after the /// semantic end of the sound will be preserved when the sound /// loops. pub fn new_loop(sound_handle: &SoundHandle, settings: LoopArrangementSettings) -> Self { let duration = sound_handle .semantic_duration() .unwrap_or(sound_handle.duration()); let mut arrangement = Self::new(ArrangementSettings { id: settings.id, default_track: settings.default_track, cooldown: settings.cooldown, semantic_duration: settings.semantic_duration, default_loop_start: Some(duration), groups: settings.groups, }); arrangement .add_clip(SoundClip::new(sound_handle, 0.0)) .add_clip(SoundClip::new(sound_handle, duration).trim(duration)); arrangement } /// Creates a new arrangement that plays an intro sound, then /// seamlessly loops another sound. /// /// If the intro has a semantic duration, it will be used to determine /// when the loop sound starts. If the loop sound has a semantic duration, /// it will be used to set the point where the sound repeats. Any audio /// after the semantic end of the sound will be preserved when the sound /// loops. pub fn new_loop_with_intro( intro_sound_handle: &SoundHandle, loop_sound_handle: &SoundHandle, settings: LoopArrangementSettings, ) -> Self { let intro_duration = intro_sound_handle .semantic_duration() .unwrap_or(intro_sound_handle.duration()); let loop_duration = loop_sound_handle .semantic_duration() .unwrap_or(loop_sound_handle.duration()); let mut arrangement = Self::new(ArrangementSettings { id: settings.id, default_track: settings.default_track, cooldown: settings.cooldown, semantic_duration: settings.semantic_duration, default_loop_start: Some(intro_duration + loop_duration), groups: settings.groups, }); arrangement .add_clip(SoundClip::new(intro_sound_handle, 0.0)) .add_clip(SoundClip::new(loop_sound_handle, intro_duration)) .add_clip( SoundClip::new(loop_sound_handle, intro_duration + loop_duration) .trim(loop_duration), ); arrangement } /// Adds a sound clip to the arrangement. pub fn add_clip(&mut self, clip: SoundClip) -> &mut Self { self.duration = self.duration.max(clip.clip_time_range.1); self.clips.push(clip); self } /// Gets the unique identifier for this arrangement. pub fn id(&self) -> ArrangementId { self.id } /// Gets the duration of the arrangement. /// /// The duration is always the end of the last playing sound clip. pub fn duration(&self) -> f64 { self.duration } /// Gets the default track instances of this arrangement will play on. pub fn default_track(&self) -> TrackIndex { self.default_track } /// Gets the groups this arrangement belongs to. pub fn groups(&self) -> &GroupSet { &self.groups } /// Gets the "musical length" of the arrangement (if there is one). pub fn semantic_duration(&self) -> Option<f64> { self.semantic_duration } /// Returns the default time (in seconds) instances /// of this arrangement will loop back to when they reach /// the end. pub fn default_loop_start(&self) -> Option<f64> { self.default_loop_start } /// Gets the frame at the given position of the arrangement. pub(crate) fn get_frame_at_position( &self, position: f64, sounds: &StaticIndexMap<SoundId, Owned<Sound>>, ) -> Frame { let mut frame = Frame::from_mono(0.0); for clip in &self.clips { frame += clip.get_frame_at_position(position, sounds); } frame } /// Starts the cooldown timer for the arrangement. pub(crate) fn start_cooldown(&mut self) { if let Some(cooldown) = self.cooldown { self.cooldown_timer = cooldown; } } /// Updates the cooldown timer for the arrangement. pub(crate) fn update_cooldown(&mut self, dt: f64) { if self.cooldown_timer > 0.0 { self.cooldown_timer -= dt; } } /// Gets whether the arrangement is currently "cooling down". /// /// If it is, a new instance of the arrangement should not /// be started until the timer is up. pub(crate) fn cooling_down(&self) -> bool { self.cooldown_timer > 0.0 } /// Returns if this arrangement is in the group with the given ID. pub(crate) fn is_in_group(&self, id: GroupId, all_groups: &Groups) -> bool { self.groups.has_ancestor(id, all_groups) } }