Skip to main content

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}