fyrox_sound/buffer/mod.rs
1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! This module provides all needed types and methods to create/load sound buffers from different sources.
22//!
23//! # Overview
24//!
25//! Buffer is data source for sound sources in the engine. Each sound sound will fetch samples it needs
26//! from a buffer, process them and send to output device. Buffer can be shared across multiple sources,
27//! this is why each instance wrapped into `Arc<Mutex<>>`. Why not just load a buffer per source? This
28//! is just inefficient memory-wise. Sound samples are very heavy: for example a mono sound that lasts
29//! just 1 second will take ~172 Kb of memory (with 44100 Hz sampling rate and float sample representation).
30
31use crate::{
32 buffer::{generic::GenericBuffer, streaming::StreamingBuffer},
33 error::SoundError,
34};
35use fyrox_core::{
36 io::FileError, reflect::prelude::*, uuid, uuid::Uuid, visitor::prelude::*, TypeUuidProvider,
37};
38use fyrox_resource::untyped::ResourceKind;
39use fyrox_resource::{
40 io::{FileReader, ResourceIo},
41 Resource, ResourceData,
42};
43use std::{
44 error::Error,
45 fmt::Debug,
46 io::{Cursor, Read, Seek, SeekFrom},
47 ops::{Deref, DerefMut},
48 path::{Path, PathBuf},
49 time::Duration,
50};
51use symphonia::core::io::MediaSource;
52
53pub mod generic;
54pub mod loader;
55pub mod streaming;
56
57/// Data source enumeration. Provides unified way of selecting data source for sound buffers. It can be either
58/// a file or memory block.
59#[derive(Debug)]
60pub enum DataSource {
61 /// Data source is a file of any supported format.
62 File {
63 /// Path to file.
64 path: PathBuf,
65
66 /// Reader for reading from the source
67 data: Box<dyn FileReader>,
68 },
69
70 /// Data source is a memory block. Memory block must be in valid format (wav or vorbis/ogg). This variant can
71 /// be used together with virtual file system.
72 Memory(Cursor<Vec<u8>>),
73
74 /// Raw samples in interleaved format with specified sample rate and channel count. Can be used for procedural
75 /// sounds.
76 ///
77 /// # Notes
78 ///
79 /// Cannot be used with streaming buffers - it makes no sense to stream data that is already loaded into memory.
80 Raw {
81 /// Sample rate, typical values 22050, 44100, 48000, etc.
82 sample_rate: usize,
83
84 /// Total amount of channels.
85 channel_count: usize,
86
87 /// Raw samples in interleaved format. Count of samples must be multiple to channel count, otherwise you'll
88 /// get error at attempt to use such buffer.
89 samples: Vec<f32>,
90 },
91
92 /// Raw streaming source.
93 RawStreaming(Box<dyn RawStreamingDataSource>),
94}
95
96/// A samples generator.
97///
98/// # Notes
99///
100/// Iterator implementation (the `next()` method) must produce samples in interleaved format, this
101/// means that samples emitted by the method should be in `LRLRLR..` order, where `L` and `R` are
102/// samples from left and right channels respectively. The sound engine supports both mono and
103/// stereo sample sources.
104pub trait RawStreamingDataSource: Iterator<Item = f32> + Send + Sync + Debug {
105 /// Should return sample rate of the source.
106 fn sample_rate(&self) -> usize;
107
108 /// Should return total channel count.
109 fn channel_count(&self) -> usize;
110
111 /// Tells whether the provider should restart.
112 ///
113 /// Default implementation calls [`Self::time_seek`] with a zero duration
114 fn rewind(&mut self) -> Result<(), SoundError> {
115 self.time_seek(Duration::from_secs(0))
116 }
117
118 /// Allows you to start playback from given duration.
119 fn time_seek(&mut self, _duration: Duration) -> Result<(), SoundError> {
120 Ok(())
121 }
122
123 /// Returns total duration of the data.
124 fn channel_duration_in_samples(&self) -> usize {
125 0
126 }
127}
128
129impl DataSource {
130 /// Tries to create new `File` data source from given path. May fail if file does not exists.
131 pub async fn from_file<P>(path: P, io: &dyn ResourceIo) -> Result<Self, FileError>
132 where
133 P: AsRef<Path>,
134 {
135 Ok(DataSource::File {
136 path: path.as_ref().to_path_buf(),
137 data: io.file_reader(path.as_ref()).await?,
138 })
139 }
140
141 /// Creates new data source from given memory block. This function does not checks if this is valid source or
142 /// not. Data source validity will be checked on first use.
143 pub fn from_memory(data: Vec<u8>) -> Self {
144 DataSource::Memory(Cursor::new(data))
145 }
146
147 /// Tries to get a path to external data source.
148 pub fn path(&self) -> Option<&Path> {
149 match self {
150 DataSource::File { path, .. } => Some(path),
151 _ => None,
152 }
153 }
154
155 /// Tries to get a path to external data source.
156 pub fn path_owned(&self) -> Option<PathBuf> {
157 match self {
158 DataSource::File { path, .. } => Some(path.clone()),
159 _ => None,
160 }
161 }
162}
163
164impl Read for DataSource {
165 fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
166 match self {
167 DataSource::File { data, .. } => data.read(buf),
168 DataSource::Memory(b) => b.read(buf),
169 DataSource::Raw { .. } => unreachable!("Raw data source does not supports Read trait!"),
170 DataSource::RawStreaming { .. } => {
171 unreachable!("Raw data source does not supports Read trait!")
172 }
173 }
174 }
175}
176
177impl Seek for DataSource {
178 fn seek(&mut self, pos: SeekFrom) -> Result<u64, std::io::Error> {
179 match self {
180 DataSource::File { data, .. } => data.seek(pos),
181 DataSource::Memory(b) => b.seek(pos),
182 DataSource::Raw { .. } => unreachable!("Raw data source does not supports Seek trait!"),
183 DataSource::RawStreaming { .. } => {
184 unreachable!("Raw data source does not supports Seek trait!")
185 }
186 }
187 }
188}
189
190impl MediaSource for DataSource {
191 fn is_seekable(&self) -> bool {
192 match self {
193 DataSource::File { .. } | DataSource::Memory(_) => true,
194 DataSource::Raw { .. } | DataSource::RawStreaming(_) => false,
195 }
196 }
197
198 fn byte_len(&self) -> Option<u64> {
199 match self {
200 DataSource::File { path: _, data } => data.byte_len(),
201 DataSource::Memory(cursor) => MediaSource::byte_len(cursor),
202 DataSource::Raw { .. } | DataSource::RawStreaming(_) => None,
203 }
204 }
205}
206
207/// An error that can occur during loading of sound buffer.
208#[derive(Debug)]
209pub enum SoundBufferResourceLoadError {
210 /// A format is not supported.
211 UnsupportedFormat,
212 /// File load error.
213 Io(FileError),
214 /// Errors involving the data source
215 ///
216 /// This could be, e.g., wrong number of channels, or attempting to use a
217 /// [`DataSource::RawStreaming`] where it doesn't make sense
218 DataSourceError,
219 /// Underlying sound error
220 SoundError(SoundError),
221}
222
223impl std::fmt::Display for SoundBufferResourceLoadError {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 match self {
226 SoundBufferResourceLoadError::UnsupportedFormat => {
227 write!(f, "unsupported file format")
228 }
229 SoundBufferResourceLoadError::Io(e) => write!(f, "{e:?}"),
230 SoundBufferResourceLoadError::DataSourceError => {
231 write!(f, "error in underlying data source")
232 }
233 SoundBufferResourceLoadError::SoundError(e) => write!(f, "{e:?}"),
234 }
235 }
236}
237
238impl std::error::Error for SoundBufferResourceLoadError {}
239
240/// Sound buffer is a data source for sound sources. See module documentation for more info.
241#[derive(Debug, Visit, Reflect)]
242#[reflect(non_cloneable)]
243pub enum SoundBuffer {
244 /// General-purpose buffer, usually contains all the data and allows random
245 /// access to samples. It is also used to make streaming buffer via composition.
246 Generic(GenericBuffer),
247
248 /// Buffer that will be filled by small portions of data only when it is needed.
249 /// Ideal for large sounds (music, ambient, etc.), because unpacked PCM data
250 /// takes very large amount of RAM. Allows random access only to currently loaded
251 /// block, so in general there is no *true* random access.
252 Streaming(StreamingBuffer),
253}
254
255impl From<SoundError> for SoundBufferResourceLoadError {
256 fn from(err: SoundError) -> Self {
257 Self::SoundError(err)
258 }
259}
260
261/// Type alias for sound buffer resource.
262pub type SoundBufferResource = Resource<SoundBuffer>;
263
264/// Extension trait for sound buffer resource.
265pub trait SoundBufferResourceExtension {
266 /// Tries to create new streaming sound buffer from a given data source.
267 fn new_streaming(
268 data_source: DataSource,
269 ) -> Result<Resource<SoundBuffer>, SoundBufferResourceLoadError>;
270
271 /// Tries to create new generic sound buffer from a given data source.
272 fn new_generic(
273 data_source: DataSource,
274 ) -> Result<Resource<SoundBuffer>, SoundBufferResourceLoadError>;
275}
276
277impl SoundBufferResourceExtension for SoundBufferResource {
278 fn new_streaming(
279 data_source: DataSource,
280 ) -> Result<Resource<SoundBuffer>, SoundBufferResourceLoadError> {
281 Ok(Resource::new_ok(
282 Uuid::new_v4(),
283 ResourceKind::External,
284 SoundBuffer::Streaming(StreamingBuffer::new(data_source)?),
285 ))
286 }
287
288 fn new_generic(
289 data_source: DataSource,
290 ) -> Result<Resource<SoundBuffer>, SoundBufferResourceLoadError> {
291 Ok(Resource::new_ok(
292 Uuid::new_v4(),
293 ResourceKind::External,
294 SoundBuffer::Generic(GenericBuffer::new(data_source)?),
295 ))
296 }
297}
298
299impl TypeUuidProvider for SoundBuffer {
300 fn type_uuid() -> Uuid {
301 uuid!("f6a077b7-c8ff-4473-a95b-0289441ea9d8")
302 }
303}
304
305impl SoundBuffer {
306 /// Tries to create new streaming sound buffer from a given data source. It returns raw sound
307 /// buffer that has to be wrapped into Arc<Mutex<>> for use with sound sources.
308 pub fn raw_streaming(data_source: DataSource) -> Result<Self, SoundBufferResourceLoadError> {
309 Ok(Self::Streaming(StreamingBuffer::new(data_source)?))
310 }
311
312 /// Tries to create new generic sound buffer from a given data source. It returns raw sound
313 /// buffer that has to be wrapped into Arc<Mutex<>> for use with sound sources.
314 pub fn raw_generic(data_source: DataSource) -> Result<Self, SoundBufferResourceLoadError> {
315 Ok(Self::Generic(GenericBuffer::new(data_source)?))
316 }
317}
318
319impl Default for SoundBuffer {
320 fn default() -> Self {
321 SoundBuffer::Generic(Default::default())
322 }
323}
324
325impl Deref for SoundBuffer {
326 type Target = GenericBuffer;
327
328 /// Returns shared reference to generic buffer for any enum variant. It is possible because
329 /// streaming sound buffers are built on top of generic buffers.
330 fn deref(&self) -> &Self::Target {
331 match self {
332 SoundBuffer::Generic(v) => v,
333 SoundBuffer::Streaming(v) => v,
334 }
335 }
336}
337
338impl DerefMut for SoundBuffer {
339 /// Returns mutable reference to generic buffer for any enum variant. It is possible because
340 /// streaming sound buffers are built on top of generic buffers.
341 fn deref_mut(&mut self) -> &mut Self::Target {
342 match self {
343 SoundBuffer::Generic(v) => v,
344 SoundBuffer::Streaming(v) => v,
345 }
346 }
347}
348
349impl ResourceData for SoundBuffer {
350 fn type_uuid(&self) -> Uuid {
351 uuid!("f6a077b7-c8ff-4473-a95b-0289441ea9d8")
352 }
353
354 fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
355 Err("Saving is not supported!".to_string().into())
356 }
357
358 fn can_be_saved(&self) -> bool {
359 false
360 }
361
362 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
363 None
364 }
365}