kira_loaders/
lib.rs

1/*!
2# kira-loaders
3
4Provides support for loading and streaming sounds from audio files
5in Kira.
6
7## Examples
8
9### Loading a sound into memory all at once
10
11```no_run
12use kira::{
13	manager::{backend::MockBackend, AudioManager, AudioManagerSettings},
14	sound::static_sound::StaticSoundSettings,
15};
16
17const SAMPLE_RATE: u32 = 48_000;
18let mut manager = AudioManager::new(
19	MockBackend::new(SAMPLE_RATE),
20	AudioManagerSettings::default(),
21)
22.unwrap();
23manager.play(kira_loaders::load(
24	"sound.ogg",
25	StaticSoundSettings::default(),
26)?)?;
27# Result::<(), Box<dyn std::error::Error>>::Ok(())
28```
29
30### Streaming a sound from disk
31
32```no_run
33use kira::manager::{backend::MockBackend, AudioManager, AudioManagerSettings};
34use kira_loaders::StreamingSoundSettings;
35
36const SAMPLE_RATE: u32 = 48_000;
37let mut manager = AudioManager::new(
38	MockBackend::new(SAMPLE_RATE),
39	AudioManagerSettings::default(),
40)
41.unwrap();
42manager.play(kira_loaders::stream(
43	"sound.ogg",
44	StreamingSoundSettings::default(),
45)?)?;
46# Result::<(), Box<dyn std::error::Error>>::Ok(())
47```
48
49## Static vs. streaming sounds
50
51`kira-loaders` can load entire sounds into memory, but it can also
52stream them from the disk in real-time. This reduces the amount of
53memory needed to play large audio files.
54
55The [`stream`] function takes a [`StreamingSoundSettings`] argument,
56which is almost the same as [`StaticSoundSettings`]. Similarly,
57[`StreamingSoundHandle`]s are very similar to
58[`StaticSoundHandle`](kira::sound::static_sound::StaticSoundHandle)s.
59
60Streaming sounds have some disadvantages compared to static sounds:
61
62- Streaming sounds require more CPU power.
63- There may be a longer delay between when you call
64  [`AudioManager::play`](kira::manager::AudioManager::play) and
65  when the sound actually starts playing.
66- Seeking the sound may also have a longer delay.
67- If the file cannot be read from the disk fast enough, there will be hiccups in
68  the sound playback. (This will not affect other sounds, though.)
69- Backwards playback is not supported.
70- [`StreamingSoundData`] cannot be cloned.
71*/
72
73#![warn(missing_docs)]
74#![allow(clippy::tabs_in_doc_comments)]
75
76mod error;
77mod streaming;
78
79use std::{fs::File, io::Cursor, path::Path, sync::Arc};
80
81pub use error::*;
82use kira::{
83	dsp::Frame,
84	sound::static_sound::{StaticSoundData, StaticSoundSettings},
85};
86use streaming::decoder::symphonia::SymphoniaDecoder;
87pub use streaming::*;
88use symphonia::core::{
89	audio::{AudioBuffer, AudioBufferRef, Signal},
90	conv::{FromSample, IntoSample},
91	io::{MediaSource, MediaSourceStream},
92	sample::Sample,
93};
94
95fn load_from_media_source(
96	media_source: Box<dyn MediaSource>,
97	settings: StaticSoundSettings,
98) -> Result<StaticSoundData, Error> {
99	let codecs = symphonia::default::get_codecs();
100	let probe = symphonia::default::get_probe();
101	let mss = MediaSourceStream::new(media_source, Default::default());
102	let mut format_reader = probe
103		.format(
104			&Default::default(),
105			mss,
106			&Default::default(),
107			&Default::default(),
108		)?
109		.format;
110	let codec_params = &format_reader
111		.default_track()
112		.ok_or(Error::NoDefaultTrack)?
113		.codec_params;
114	let sample_rate = codec_params.sample_rate.ok_or(Error::UnknownSampleRate)?;
115	let mut decoder = codecs.make(codec_params, &Default::default())?;
116	let mut frames = vec![];
117	loop {
118		match format_reader.next_packet() {
119			Ok(packet) => {
120				let buffer = decoder.decode(&packet)?;
121				load_frames_from_buffer_ref(&mut frames, &buffer)?;
122			}
123			Err(error) => match error {
124				symphonia::core::errors::Error::IoError(error) => {
125					if error.kind() == std::io::ErrorKind::UnexpectedEof {
126						break;
127					}
128					return Err(symphonia::core::errors::Error::IoError(error).into());
129				}
130				error => return Err(error.into()),
131			},
132		}
133	}
134	Ok(StaticSoundData {
135		sample_rate,
136		frames: Arc::new(frames),
137		settings,
138	})
139}
140
141/// Loads an audio file into a [`StaticSoundData`].
142pub fn load(
143	path: impl AsRef<Path>,
144	settings: StaticSoundSettings,
145) -> Result<StaticSoundData, Error> {
146	load_from_media_source(Box::new(File::open(path)?), settings)
147}
148
149/// Loads a cursor wrapping audio file data into a [`StaticSoundData`].
150pub fn load_from_cursor<T: AsRef<[u8]> + Send + 'static>(
151	cursor: Cursor<T>,
152	settings: StaticSoundSettings,
153) -> Result<StaticSoundData, Error> {
154	load_from_media_source(Box::new(cursor), settings)
155}
156
157/// Creates a [`StreamingSoundData`] for an audio file.
158pub fn stream(
159	path: impl AsRef<Path>,
160	settings: StreamingSoundSettings,
161) -> Result<StreamingSoundData<Error>, Error> {
162	Ok(StreamingSoundData {
163		decoder: Box::new(SymphoniaDecoder::new(Box::new(File::open(path)?))?),
164		settings,
165	})
166}
167
168/// Creates a [`StreamingSoundData`] for a cursor wrapping audio file data.
169pub fn stream_from_cursor<T: AsRef<[u8]> + Send + 'static>(
170	cursor: Cursor<T>,
171	settings: StreamingSoundSettings,
172) -> Result<StreamingSoundData<Error>, Error> {
173	Ok(StreamingSoundData {
174		decoder: Box::new(SymphoniaDecoder::new(Box::new(cursor))?),
175		settings,
176	})
177}
178
179fn load_frames_from_buffer_ref(
180	frames: &mut Vec<Frame>,
181	buffer: &AudioBufferRef,
182) -> Result<(), Error> {
183	match buffer {
184		AudioBufferRef::U8(buffer) => load_frames_from_buffer(frames, buffer),
185		AudioBufferRef::U16(buffer) => load_frames_from_buffer(frames, buffer),
186		AudioBufferRef::U24(buffer) => load_frames_from_buffer(frames, buffer),
187		AudioBufferRef::U32(buffer) => load_frames_from_buffer(frames, buffer),
188		AudioBufferRef::S8(buffer) => load_frames_from_buffer(frames, buffer),
189		AudioBufferRef::S16(buffer) => load_frames_from_buffer(frames, buffer),
190		AudioBufferRef::S24(buffer) => load_frames_from_buffer(frames, buffer),
191		AudioBufferRef::S32(buffer) => load_frames_from_buffer(frames, buffer),
192		AudioBufferRef::F32(buffer) => load_frames_from_buffer(frames, buffer),
193		AudioBufferRef::F64(buffer) => load_frames_from_buffer(frames, buffer),
194	}
195}
196
197fn load_frames_from_buffer<S: Sample>(
198	frames: &mut Vec<Frame>,
199	buffer: &AudioBuffer<S>,
200) -> Result<(), Error>
201where
202	f32: FromSample<S>,
203{
204	match buffer.spec().channels.count() {
205		1 => {
206			for sample in buffer.chan(0) {
207				frames.push(Frame::from_mono((*sample).into_sample()));
208			}
209		}
210		2 => {
211			for (left, right) in buffer.chan(0).iter().zip(buffer.chan(1).iter()) {
212				frames.push(Frame::new((*left).into_sample(), (*right).into_sample()));
213			}
214		}
215		_ => return Err(Error::UnsupportedChannelConfiguration),
216	}
217	Ok(())
218}