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 act as a transition as we go 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)
	}
}