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
//! `rusty_audio` is a convenient sound library for small projects and educational purposes. For //! more elaborate needs, please use [rodio](https://github.com/tomaka/rodio), which is the much //! more powerful audio library that this one uses. //! //! Example //! ======= //! ``` //! use rusty_audio::Audio; //! let mut audio = Audio::new(); //! audio.add("startup", "audio_subsystem_initialized.mp3"); // Load the sound, give it a name //! audio.play("startup"); // Execution continues while playback occurs in another thread. //! audio.wait(); // Block until sounds finish playing //! ``` use rodio::{ source::{Buffered, Source}, Decoder, OutputStream, OutputStreamHandle, Sink, }; use std::collections::HashMap; use std::fs::File; use std::io::{Cursor, Read}; pub mod prelude { pub use crate::Audio; } /// A simple 4-track audio system to load/decode audio files from disk to play later. Supported /// formats are: MP3, WAV, Vorbis and Flac. #[derive(Default)] pub struct Audio { clips: HashMap<&'static str, Buffered<Decoder<Cursor<Vec<u8>>>>>, channels: Vec<Sink>, current_channel: usize, output: Option<(OutputStream, OutputStreamHandle)>, } impl Audio { /// Create a new sound subsystem. You only need one of these -- you can use it to load and play /// any number of audio clips. pub fn new() -> Self { if let Ok(output) = OutputStream::try_default() { let clips = HashMap::new(); let mut channels: Vec<Sink> = Vec::new(); for i in 0..4 { let sink = Sink::try_new(&output.1) .unwrap_or_else(|_| panic!("Failed to create sound channel {}", i)); channels.push(sink); } Self { clips, channels, current_channel: 0, output: Some(output), } } else { Self { clips: HashMap::new(), channels: Vec::new(), current_channel: 0, output: None, } } } /// If no sound device was detected, the audio subsystem will run in a disabled mode that /// doesn't actually do anything. This method indicates whether audio is disabled. pub fn disabled(&self) -> bool { self.output.is_none() } /// Add an audio clip to play. Audio clips will be decoded and buffered during this call so /// the first call to `.play()` is not staticky if you compile in debug mode. `name` is what /// you will refer to this clip as when you need to play it. Files known to be supported by the /// underlying library (rodio) at the time of this writing are MP3, WAV, Vorbis and Flac. pub fn add(&mut self, name: &'static str, path: &str) { if self.disabled() { return; } let mut file_vec: Vec<u8> = Vec::new(); File::open(path) .expect("Couldn't find audio file to add.") .read_to_end(&mut file_vec) .expect("Failed reading in opened audio file."); let cursor = Cursor::new(file_vec); let decoder = Decoder::new(cursor).unwrap(); let buffered = decoder.buffered(); // Buffers are lazily decoded, which often leads to static on first play on low-end systems // or when you compile in debug mode. Since this library is intended for educational // projects, those are going to be common conditions. So, to optimize for our use-case, we // will pre-warm all of our audio buffers by forcing things to be decoded and cached right // now when we first load the file. I would like to find a cleaner way to do this, but the // following scheme (iterating through a clone and discarding the decoded frames) works // since clones of a Buffered share the actual decoded data buffer cache by means of Arc and // Mutex. let warm = buffered.clone(); for i in warm { #[allow(clippy::drop_copy)] drop(i); } self.clips.insert(name, buffered); } /// Play an audio clip that has already been loaded. `name` is the name you chose when you /// added the clip to the `Audio` system. If you forgot to load the clip first, this will crash. pub fn play(&mut self, name: &str) { if self.disabled() { return; } let buffer = self.clips.get(name).expect("No clip by that name.").clone(); self.channels[self.current_channel].append(buffer); self.current_channel += 1; if self.current_channel >= self.channels.len() { self.current_channel = 0; } } /// Block until no sounds are playing. Convenient for keeping a thread alive until all sounds /// have played. pub fn wait(&self) { if self.disabled() { return; } loop { if self.channels.iter().any(|x| !x.empty()) { std::thread::sleep(std::time::Duration::from_millis(50)); continue; } break; } } }