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
//! Functions and types relating to audio playback. use std::fmt::{self, Debug, Formatter}; use std::path::Path; use std::sync::Arc; use crate::error::Result; use crate::fs; use crate::platform::AudioControls; use crate::Context; /// Sound data that can be played back. /// /// Supports WAV, Ogg Vorbis, MP3 and FLAC (in other words, everything that /// [Rodio](https://github.com/tomaka/rodio) provides support for). /// /// All of the playback methods on this type return a [`SoundInstance`](./struct.SoundInstance.html) that /// can be used to control the sound after it has started. If you just want /// to 'fire and forget' a sound, you can discard it - the sound will /// continue playing regardless. /// /// This type acts as a lightweight handle to the associated audio data, /// and so can be cloned with little overhead. #[derive(Debug, Clone, PartialEq)] pub struct Sound { pub(crate) data: Arc<[u8]>, } impl Sound { /// Creates a new sound from the given file. /// /// Note that the data is not decoded until playback begins, so this function will not /// validate that the data being read is formatted correctly. /// /// # Errors /// /// * `TetraError::FailedToLoadAsset` will be returned if the file could not be loaded. pub fn new<P>(path: P) -> Result<Sound> where P: AsRef<Path>, { Ok(Sound { data: fs::read(path)?.into(), }) } /// Creates a new sound from a slice of binary data, encoded in one of Tetra's supported /// file formats. /// /// This is useful in combination with `include_bytes`, as it allows you to include /// your audio data directly in the binary. /// /// Note that the data is not decoded until playback begins, so this function will not /// validate that the data being read is formatted correctly. pub fn from_file_data(data: &[u8]) -> Sound { Sound { data: data.into() } } /// Plays the sound. /// /// # Errors /// /// * `TetraError::NoAudioDevice` will be returned if no audio device is active. /// * `TetraError::InvalidSound` will be returned if the sound data could not be decoded. pub fn play(&self, ctx: &Context) -> Result<SoundInstance> { ctx.audio .play_sound(Arc::clone(&self.data), true, false, 1.0, 1.0) .map(|controls| SoundInstance { controls }) } /// Plays the sound repeatedly. /// /// # Errors /// /// * `TetraError::NoAudioDevice` will be returned if no audio device is active. /// * `TetraError::InvalidSound` will be returned if the sound data could not be decoded. pub fn repeat(&self, ctx: &Context) -> Result<SoundInstance> { ctx.audio .play_sound(Arc::clone(&self.data), true, true, 1.0, 1.0) .map(|controls| SoundInstance { controls }) } /// Spawns a new instance of the sound that is not playing yet. /// /// # Errors /// /// * `TetraError::NoAudioDevice` will be returned if no audio device is active. /// * `TetraError::InvalidSound` will be returned if the sound data could not be decoded. pub fn spawn(&self, ctx: &Context) -> Result<SoundInstance> { ctx.audio .play_sound(Arc::clone(&self.data), false, false, 1.0, 1.0) .map(|controls| SoundInstance { controls }) } /// Plays the sound, with the provided settings. /// /// # Errors /// /// * `TetraError::NoAudioDevice` will be returned if no audio device is active. /// * `TetraError::InvalidSound` will be returned if the sound data could not be decoded. pub fn play_with(&self, ctx: &Context, volume: f32, speed: f32) -> Result<SoundInstance> { ctx.audio .play_sound(Arc::clone(&self.data), true, false, volume, speed) .map(|controls| SoundInstance { controls }) } /// Plays the sound repeatedly, with the provided settings. /// /// # Errors /// /// * `TetraError::NoAudioDevice` will be returned if no audio device is active. /// * `TetraError::InvalidSound` will be returned if the sound data could not be decoded. pub fn repeat_with(&self, ctx: &Context, volume: f32, speed: f32) -> Result<SoundInstance> { ctx.audio .play_sound(Arc::clone(&self.data), true, true, volume, speed) .map(|controls| SoundInstance { controls }) } /// Spawns a new instance of the sound that is not playing yet, with the provided settings. /// /// # Errors /// /// * `TetraError::NoAudioDevice` will be returned if no audio device is active. /// * `TetraError::InvalidSound` will be returned if the sound data could not be decoded. pub fn spawn_with(&self, ctx: &Context, volume: f32, speed: f32) -> Result<SoundInstance> { ctx.audio .play_sound(Arc::clone(&self.data), false, false, volume, speed) .map(|controls| SoundInstance { controls }) } } /// A handle to a single instance of a [`Sound`](./struct.Sound.html). /// /// The audio thread will poll this for updates every 220 samples (roughly /// every 5ms at a 44100hz sample rate). /// /// Cloning a `SoundInstance` will create a new handle to the same instance, /// rather than creating a new instance. /// /// Note that dropping a `SoundInstance` does not stop playback. #[derive(Clone)] pub struct SoundInstance { pub(crate) controls: Arc<AudioControls>, } impl SoundInstance { /// Plays the sound if it is stopped, or resumes the sound if it is paused. pub fn play(&self) { self.controls.play(); } /// Stops the sound, and rewinds it to the beginning. pub fn stop(&self) { self.controls.stop(); } /// Pauses the sound. pub fn pause(&self) { self.controls.pause(); } /// Sets the volume of the sound. /// /// The parameter is used as a multiplier - for example, `1.0` would result in the /// sound being played back at its original volume. pub fn set_volume(&self, volume: f32) { self.controls.set_volume(volume); } /// Sets the speed (and by extension, the pitch) of the sound. /// /// The parameter is used as a multiplier - for example, `1.0` would result in the /// sound being played back at its original speed. pub fn set_speed(&self, speed: f32) { self.controls.set_speed(speed); } /// Sets whether the sound should repeat or not. pub fn set_repeating(&self, repeating: bool) { self.controls.set_repeating(repeating); } /// Toggles whether the sound should repeat or not. pub fn toggle_repeating(&self) { self.controls.set_repeating(!self.controls.repeating()); } } // TODO: Remove or make more useful in 0.4. impl Debug for SoundInstance { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("SoundInstance") .field("controls", &"<platform internals>") .finish() } } /// Sets the master volume for the game. /// /// The parameter is used as a multiplier - for example, `1.0` would result in /// sounds being played back at their original volume. pub fn set_master_volume(ctx: &mut Context, volume: f32) { ctx.audio.set_master_volume(volume); } /// Gets the master volume for the game. pub fn get_master_volume(ctx: &mut Context) -> f32 { ctx.audio.master_volume() }