chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//! Generic buffer module.
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! Generic buffer is just an array of raw samples in IEEE float format with some additional info (sample rate,
|
||||
//! channel count, etc.). All samples stored in interleaved format, which means samples for each channel are
|
||||
//! near - `LRLRLR...`.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Generic source can be created like so:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use std::sync::{Mutex, Arc};
|
||||
//! use fyrox_sound::buffer::{SoundBufferResource, DataSource, SoundBufferResourceExtension};
|
||||
//! use fyrox_resource::io::FsResourceIo;
|
||||
//!
|
||||
//! async fn make_buffer() -> SoundBufferResource {
|
||||
//! let data_source = DataSource::from_file("sound.wav", &FsResourceIo).await.unwrap();
|
||||
//! SoundBufferResource::new_generic(data_source).unwrap()
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
use crate::{buffer::DataSource, decoder::Decoder};
|
||||
use fyrox_core::{reflect::prelude::*, visitor::prelude::*};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::time::Duration;
|
||||
|
||||
use super::SoundBufferResourceLoadError;
|
||||
|
||||
/// Samples container.
|
||||
#[derive(Debug, Default, PartialEq, Visit, Clone, Reflect)]
|
||||
#[reflect(type_uuid = "7b0ba106-9ed5-41e1-a8c7-82c609d6f74e")]
|
||||
pub struct Samples(pub Vec<f32>);
|
||||
|
||||
impl Deref for Samples {
|
||||
type Target = Vec<f32>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Samples {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic sound buffer that contains decoded samples and allows random access.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Visit, Reflect)]
|
||||
#[reflect(type_uuid = "e5bf74a5-13d9-4518-96f2-0e4f3413966e")]
|
||||
pub struct GenericBuffer {
|
||||
/// Interleaved decoded samples (mono sounds: L..., stereo sounds: LR...)
|
||||
/// For streaming buffers it contains only small part of decoded data
|
||||
/// (usually something around 1 sec).
|
||||
#[visit(skip)]
|
||||
pub(crate) samples: Samples,
|
||||
#[visit(skip)]
|
||||
pub(crate) channel_count: usize,
|
||||
#[visit(skip)]
|
||||
pub(crate) sample_rate: usize,
|
||||
#[visit(skip)]
|
||||
pub(crate) channel_duration_in_samples: usize,
|
||||
}
|
||||
|
||||
impl GenericBuffer {
|
||||
/// Creates new generic buffer from specified data source. May fail if data source has unsupported
|
||||
/// format, corrupted, etc.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// `DataSource::RawStreaming` is **not** supported with generic buffers, use streaming buffer
|
||||
/// instead!
|
||||
///
|
||||
/// Data source with raw samples must have sample count multiple of channel count, otherwise this
|
||||
/// function will return `Err`.
|
||||
pub fn new(source: DataSource) -> Result<Self, SoundBufferResourceLoadError> {
|
||||
match source {
|
||||
DataSource::Raw {
|
||||
sample_rate,
|
||||
channel_count,
|
||||
samples,
|
||||
} => {
|
||||
if channel_count < 1 || channel_count > 2 || samples.len() % channel_count != 0 {
|
||||
Err(SoundBufferResourceLoadError::DataSourceError)
|
||||
} else {
|
||||
Ok(Self {
|
||||
channel_duration_in_samples: samples.len() / channel_count,
|
||||
samples: Samples(samples),
|
||||
channel_count,
|
||||
sample_rate,
|
||||
})
|
||||
}
|
||||
}
|
||||
DataSource::RawStreaming(_) => Err(SoundBufferResourceLoadError::DataSourceError),
|
||||
_ => {
|
||||
let decoder = Decoder::new(source)?;
|
||||
if decoder.get_channel_count() < 1 || decoder.get_channel_count() > 2 {
|
||||
return Err(SoundBufferResourceLoadError::DataSourceError);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
sample_rate: decoder.get_sample_rate(),
|
||||
channel_count: decoder.get_channel_count(),
|
||||
channel_duration_in_samples: decoder.channel_duration_in_samples(),
|
||||
samples: Samples(decoder.into_samples()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if buffer is empty or not.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.samples.is_empty()
|
||||
}
|
||||
|
||||
/// Returns shared reference to an array with samples.
|
||||
#[inline]
|
||||
pub fn samples(&self) -> &[f32] {
|
||||
&self.samples
|
||||
}
|
||||
|
||||
/// Returns mutable reference to an array with samples that could be modified.
|
||||
pub fn samples_mut(&mut self) -> &mut [f32] {
|
||||
&mut self.samples
|
||||
}
|
||||
|
||||
/// Returns exact amount of channels in the buffer.
|
||||
#[inline]
|
||||
pub fn channel_count(&self) -> usize {
|
||||
self.channel_count
|
||||
}
|
||||
|
||||
/// Returns sample rate of the buffer.
|
||||
#[inline]
|
||||
pub fn sample_rate(&self) -> usize {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
/// Returns exact time length of the buffer.
|
||||
#[inline]
|
||||
pub fn duration(&self) -> Duration {
|
||||
if self.sample_rate == 0 {
|
||||
return Duration::ZERO;
|
||||
}
|
||||
Duration::from_nanos(
|
||||
(self.channel_duration_in_samples as u64 * 1_000_000_000u64) / self.sample_rate as u64,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns exact duration of each channel (in samples) of the buffer. The returned value represents the entire length
|
||||
/// of each channel in the buffer, even if it is streaming and its content is not yet fully decoded.
|
||||
#[inline]
|
||||
pub fn channel_duration_in_samples(&self) -> usize {
|
||||
self.channel_duration_in_samples
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//! Sound buffer loader.
|
||||
|
||||
use crate::buffer::{DataSource, SoundBuffer};
|
||||
use fyrox_core::{reflect::prelude::*, uuid::Uuid};
|
||||
use fyrox_resource::{
|
||||
io::ResourceIo,
|
||||
loader::{BoxedImportOptionsLoaderFuture, BoxedLoaderFuture, LoaderPayload, ResourceLoader},
|
||||
options::{
|
||||
try_get_import_settings, try_get_import_settings_opaque, BaseImportOptions, ImportOptions,
|
||||
},
|
||||
state::LoadError,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
/// Defines sound buffer resource import options.
|
||||
#[derive(Clone, Deserialize, Serialize, Default, Debug, PartialEq, Reflect)]
|
||||
#[reflect(type_uuid = "032e333a-f0bd-41f2-a7e5-79207f6064ce")]
|
||||
pub struct SoundBufferImportOptions {
|
||||
/// Whether the buffer is streaming or not.
|
||||
pub stream: bool,
|
||||
}
|
||||
|
||||
impl ImportOptions for SoundBufferImportOptions {}
|
||||
|
||||
/// Default implementation for sound buffer loading.
|
||||
pub struct SoundBufferLoader {
|
||||
/// Default import options for sound buffer resources.
|
||||
pub default_import_options: SoundBufferImportOptions,
|
||||
}
|
||||
|
||||
impl ResourceLoader for SoundBufferLoader {
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&["wav", "ogg"]
|
||||
}
|
||||
|
||||
fn data_type_uuid(&self) -> Uuid {
|
||||
<SoundBuffer as Reflect>::type_info().type_uuid
|
||||
}
|
||||
|
||||
fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
|
||||
let default_import_options = self.default_import_options.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let io = io.as_ref();
|
||||
|
||||
let import_options = try_get_import_settings(&path, io)
|
||||
.await
|
||||
.unwrap_or(default_import_options);
|
||||
|
||||
let source = DataSource::from_file(&path, io)
|
||||
.await
|
||||
.map_err(LoadError::new)?;
|
||||
|
||||
let result = if import_options.stream {
|
||||
SoundBuffer::raw_streaming(source)
|
||||
} else {
|
||||
SoundBuffer::raw_generic(source)
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(buffer) => Ok(LoaderPayload::new(buffer)),
|
||||
Err(_) => Err(LoadError::new("Invalid data source.")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn try_load_import_settings(
|
||||
&self,
|
||||
resource_path: PathBuf,
|
||||
io: Arc<dyn ResourceIo>,
|
||||
) -> BoxedImportOptionsLoaderFuture {
|
||||
Box::pin(async move {
|
||||
try_get_import_settings_opaque::<SoundBufferImportOptions>(&resource_path, &*io).await
|
||||
})
|
||||
}
|
||||
|
||||
fn default_import_options(&self) -> Option<Box<dyn BaseImportOptions>> {
|
||||
Some(Box::<SoundBufferImportOptions>::default())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//! This module provides all needed types and methods to create/load sound buffers from different sources.
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! Buffer is data source for sound sources in the engine. Each sound sound will fetch samples it needs
|
||||
//! from a buffer, process them and send to output device. Buffer can be shared across multiple sources,
|
||||
//! this is why each instance wrapped into `Arc<Mutex<>>`. Why not just load a buffer per source? This
|
||||
//! is just inefficient memory-wise. Sound samples are very heavy: for example a mono sound that lasts
|
||||
//! just 1 second will take ~172 Kb of memory (with 44100 Hz sampling rate and float sample representation).
|
||||
|
||||
use crate::{
|
||||
buffer::{generic::GenericBuffer, streaming::StreamingBuffer},
|
||||
error::SoundError,
|
||||
};
|
||||
use fyrox_core::{io::FileError, reflect::prelude::*, uuid::Uuid, visitor::prelude::*};
|
||||
use fyrox_resource::untyped::ResourceKind;
|
||||
use fyrox_resource::{
|
||||
io::{FileReader, ResourceIo},
|
||||
Resource, ResourceData,
|
||||
};
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::Debug,
|
||||
io::{Cursor, Read, Seek, SeekFrom},
|
||||
ops::{Deref, DerefMut},
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
use symphonia::core::io::MediaSource;
|
||||
|
||||
pub mod generic;
|
||||
pub mod loader;
|
||||
pub mod streaming;
|
||||
|
||||
/// Data source enumeration. Provides unified way of selecting data source for sound buffers. It can be either
|
||||
/// a file or memory block.
|
||||
#[derive(Debug)]
|
||||
pub enum DataSource {
|
||||
/// Data source is a file of any supported format.
|
||||
File {
|
||||
/// Path to file.
|
||||
path: PathBuf,
|
||||
|
||||
/// Reader for reading from the source
|
||||
data: Box<dyn FileReader>,
|
||||
},
|
||||
|
||||
/// Data source is a memory block. Memory block must be in valid format (wav or vorbis/ogg). This variant can
|
||||
/// be used together with virtual file system.
|
||||
Memory(Cursor<Vec<u8>>),
|
||||
|
||||
/// Raw samples in interleaved format with specified sample rate and channel count. Can be used for procedural
|
||||
/// sounds.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// Cannot be used with streaming buffers - it makes no sense to stream data that is already loaded into memory.
|
||||
Raw {
|
||||
/// Sample rate, typical values 22050, 44100, 48000, etc.
|
||||
sample_rate: usize,
|
||||
|
||||
/// Total amount of channels.
|
||||
channel_count: usize,
|
||||
|
||||
/// Raw samples in interleaved format. Count of samples must be multiple to channel count, otherwise you'll
|
||||
/// get error at attempt to use such buffer.
|
||||
samples: Vec<f32>,
|
||||
},
|
||||
|
||||
/// Raw streaming source.
|
||||
RawStreaming(Box<dyn RawStreamingDataSource>),
|
||||
}
|
||||
|
||||
/// A samples generator.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// Iterator implementation (the `next()` method) must produce samples in interleaved format, this
|
||||
/// means that samples emitted by the method should be in `LRLRLR..` order, where `L` and `R` are
|
||||
/// samples from left and right channels respectively. The sound engine supports both mono and
|
||||
/// stereo sample sources.
|
||||
pub trait RawStreamingDataSource: Iterator<Item = f32> + Send + Sync + Debug {
|
||||
/// Should return sample rate of the source.
|
||||
fn sample_rate(&self) -> usize;
|
||||
|
||||
/// Should return total channel count.
|
||||
fn channel_count(&self) -> usize;
|
||||
|
||||
/// Tells whether the provider should restart.
|
||||
///
|
||||
/// Default implementation calls [`Self::time_seek`] with a zero duration
|
||||
fn rewind(&mut self) -> Result<(), SoundError> {
|
||||
self.time_seek(Duration::from_secs(0))
|
||||
}
|
||||
|
||||
/// Allows you to start playback from given duration.
|
||||
fn time_seek(&mut self, _duration: Duration) -> Result<(), SoundError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns total duration of the data.
|
||||
fn channel_duration_in_samples(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl DataSource {
|
||||
/// Tries to create new `File` data source from given path. May fail if file does not exists.
|
||||
pub async fn from_file<P>(path: P, io: &dyn ResourceIo) -> Result<Self, FileError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(DataSource::File {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
data: io.file_reader(path.as_ref()).await?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates new data source from given memory block. This function does not checks if this is valid source or
|
||||
/// not. Data source validity will be checked on first use.
|
||||
pub fn from_memory(data: Vec<u8>) -> Self {
|
||||
DataSource::Memory(Cursor::new(data))
|
||||
}
|
||||
|
||||
/// Tries to get a path to external data source.
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
DataSource::File { path, .. } => Some(path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to get a path to external data source.
|
||||
pub fn path_owned(&self) -> Option<PathBuf> {
|
||||
match self {
|
||||
DataSource::File { path, .. } => Some(path.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for DataSource {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
|
||||
match self {
|
||||
DataSource::File { data, .. } => data.read(buf),
|
||||
DataSource::Memory(b) => b.read(buf),
|
||||
DataSource::Raw { .. } => unreachable!("Raw data source does not supports Read trait!"),
|
||||
DataSource::RawStreaming { .. } => {
|
||||
unreachable!("Raw data source does not supports Read trait!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for DataSource {
|
||||
fn seek(&mut self, pos: SeekFrom) -> Result<u64, std::io::Error> {
|
||||
match self {
|
||||
DataSource::File { data, .. } => data.seek(pos),
|
||||
DataSource::Memory(b) => b.seek(pos),
|
||||
DataSource::Raw { .. } => unreachable!("Raw data source does not supports Seek trait!"),
|
||||
DataSource::RawStreaming { .. } => {
|
||||
unreachable!("Raw data source does not supports Seek trait!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaSource for DataSource {
|
||||
fn is_seekable(&self) -> bool {
|
||||
match self {
|
||||
DataSource::File { .. } | DataSource::Memory(_) => true,
|
||||
DataSource::Raw { .. } | DataSource::RawStreaming(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_len(&self) -> Option<u64> {
|
||||
match self {
|
||||
DataSource::File { path: _, data } => data.byte_len(),
|
||||
DataSource::Memory(cursor) => MediaSource::byte_len(cursor),
|
||||
DataSource::Raw { .. } | DataSource::RawStreaming(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that can occur during loading of sound buffer.
|
||||
#[derive(Debug)]
|
||||
pub enum SoundBufferResourceLoadError {
|
||||
/// A format is not supported.
|
||||
UnsupportedFormat,
|
||||
/// File load error.
|
||||
Io(FileError),
|
||||
/// Errors involving the data source
|
||||
///
|
||||
/// This could be, e.g., wrong number of channels, or attempting to use a
|
||||
/// [`DataSource::RawStreaming`] where it doesn't make sense
|
||||
DataSourceError,
|
||||
/// Underlying sound error
|
||||
SoundError(SoundError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SoundBufferResourceLoadError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SoundBufferResourceLoadError::UnsupportedFormat => {
|
||||
write!(f, "unsupported file format")
|
||||
}
|
||||
SoundBufferResourceLoadError::Io(e) => write!(f, "{e:?}"),
|
||||
SoundBufferResourceLoadError::DataSourceError => {
|
||||
write!(f, "error in underlying data source")
|
||||
}
|
||||
SoundBufferResourceLoadError::SoundError(e) => write!(f, "{e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for SoundBufferResourceLoadError {}
|
||||
|
||||
/// Sound buffer is a data source for sound sources. See module documentation for more info.
|
||||
#[derive(Debug, Visit, PartialEq, Reflect)]
|
||||
#[reflect(non_cloneable)]
|
||||
#[reflect(type_uuid = "f6a077b7-c8ff-4473-a95b-0289441ea9d8")]
|
||||
pub enum SoundBuffer {
|
||||
/// General-purpose buffer, usually contains all the data and allows random
|
||||
/// access to samples. It is also used to make streaming buffer via composition.
|
||||
Generic(GenericBuffer),
|
||||
|
||||
/// Buffer that will be filled by small portions of data only when it is needed.
|
||||
/// Ideal for large sounds (music, ambient, etc.), because unpacked PCM data
|
||||
/// takes very large amount of RAM. Allows random access only to currently loaded
|
||||
/// block, so in general there is no *true* random access.
|
||||
Streaming(StreamingBuffer),
|
||||
}
|
||||
|
||||
impl From<SoundError> for SoundBufferResourceLoadError {
|
||||
fn from(err: SoundError) -> Self {
|
||||
Self::SoundError(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for sound buffer resource.
|
||||
pub type SoundBufferResource = Resource<SoundBuffer>;
|
||||
|
||||
/// Extension trait for sound buffer resource.
|
||||
pub trait SoundBufferResourceExtension {
|
||||
/// Tries to create new streaming sound buffer from a given data source.
|
||||
fn new_streaming(
|
||||
data_source: DataSource,
|
||||
) -> Result<Resource<SoundBuffer>, SoundBufferResourceLoadError>;
|
||||
|
||||
/// Tries to create new generic sound buffer from a given data source.
|
||||
fn new_generic(
|
||||
data_source: DataSource,
|
||||
) -> Result<Resource<SoundBuffer>, SoundBufferResourceLoadError>;
|
||||
}
|
||||
|
||||
impl SoundBufferResourceExtension for SoundBufferResource {
|
||||
fn new_streaming(
|
||||
data_source: DataSource,
|
||||
) -> Result<Resource<SoundBuffer>, SoundBufferResourceLoadError> {
|
||||
Ok(Resource::new_ok(
|
||||
Uuid::new_v4(),
|
||||
ResourceKind::External,
|
||||
SoundBuffer::Streaming(StreamingBuffer::new(data_source)?),
|
||||
))
|
||||
}
|
||||
|
||||
fn new_generic(
|
||||
data_source: DataSource,
|
||||
) -> Result<Resource<SoundBuffer>, SoundBufferResourceLoadError> {
|
||||
Ok(Resource::new_ok(
|
||||
Uuid::new_v4(),
|
||||
ResourceKind::External,
|
||||
SoundBuffer::Generic(GenericBuffer::new(data_source)?),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl SoundBuffer {
|
||||
/// Tries to create new streaming sound buffer from a given data source. It returns raw sound
|
||||
/// buffer that has to be wrapped into Arc<Mutex<>> for use with sound sources.
|
||||
pub fn raw_streaming(data_source: DataSource) -> Result<Self, SoundBufferResourceLoadError> {
|
||||
Ok(Self::Streaming(StreamingBuffer::new(data_source)?))
|
||||
}
|
||||
|
||||
/// Tries to create new generic sound buffer from a given data source. It returns raw sound
|
||||
/// buffer that has to be wrapped into Arc<Mutex<>> for use with sound sources.
|
||||
pub fn raw_generic(data_source: DataSource) -> Result<Self, SoundBufferResourceLoadError> {
|
||||
Ok(Self::Generic(GenericBuffer::new(data_source)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SoundBuffer {
|
||||
fn default() -> Self {
|
||||
SoundBuffer::Generic(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SoundBuffer {
|
||||
type Target = GenericBuffer;
|
||||
|
||||
/// Returns shared reference to generic buffer for any enum variant. It is possible because
|
||||
/// streaming sound buffers are built on top of generic buffers.
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
SoundBuffer::Generic(v) => v,
|
||||
SoundBuffer::Streaming(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for SoundBuffer {
|
||||
/// Returns mutable reference to generic buffer for any enum variant. It is possible because
|
||||
/// streaming sound buffers are built on top of generic buffers.
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
match self {
|
||||
SoundBuffer::Generic(v) => v,
|
||||
SoundBuffer::Streaming(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceData for SoundBuffer {
|
||||
fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
|
||||
Err("Saving is not supported!".to_string().into())
|
||||
}
|
||||
|
||||
fn can_be_saved(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//! Streaming buffer.
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! Streaming buffers are used for long sounds (usually longer than 15 seconds) to reduce memory usage.
|
||||
//! Some sounds in games are very long - music, ambient sounds, voice, etc. and it is too inefficient
|
||||
//! to load and decode them directly into memory all at once - it will just take enormous amount of memory
|
||||
//! that could be used to something more useful.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! There are almost no difference with generic buffers:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use std::sync::{Mutex, Arc};
|
||||
//! use fyrox_sound::buffer::{SoundBufferResource, DataSource, SoundBufferResourceExtension};
|
||||
//! use fyrox_resource::io::FsResourceIo;
|
||||
//!
|
||||
//! async fn make_streaming_buffer() -> SoundBufferResource {
|
||||
//! let data_source = DataSource::from_file("some_long_sound.ogg", &FsResourceIo).await.unwrap();
|
||||
//! SoundBufferResource::new_streaming(data_source).unwrap()
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Notes
|
||||
//!
|
||||
//! Streaming buffer cannot be shared across multiple source. On attempt to create a source with a streaming
|
||||
//! buffer that already in use you'll get error.
|
||||
|
||||
use crate::buffer::generic::Samples;
|
||||
use crate::{
|
||||
buffer::{generic::GenericBuffer, DataSource, RawStreamingDataSource},
|
||||
decoder::Decoder,
|
||||
error::SoundError,
|
||||
};
|
||||
use fyrox_core::{reflect::prelude::*, visitor::prelude::*};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// Streaming buffer for long sounds. Does not support random access.
|
||||
#[derive(Debug, Default, Visit, Reflect)]
|
||||
#[reflect(non_cloneable)]
|
||||
#[reflect(type_uuid = "2e668c89-5998-4e66-a908-f5d96d700c9e")]
|
||||
pub struct StreamingBuffer {
|
||||
pub(crate) generic: GenericBuffer,
|
||||
/// Count of sources that share this buffer, it is important to keep only one
|
||||
/// user of streaming buffer, because streaming buffer does not allow random
|
||||
/// access.
|
||||
#[visit(skip)]
|
||||
pub(crate) use_count: usize,
|
||||
#[visit(skip)]
|
||||
#[reflect(hidden)]
|
||||
streaming_source: StreamingSource,
|
||||
}
|
||||
|
||||
impl PartialEq for StreamingBuffer {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.generic == other.generic
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
enum StreamingSource {
|
||||
#[default]
|
||||
Null,
|
||||
Decoder(Decoder),
|
||||
Raw(Box<dyn RawStreamingDataSource>),
|
||||
}
|
||||
|
||||
impl StreamingSource {
|
||||
#[inline]
|
||||
fn new(data_source: DataSource) -> Result<Self, SoundError> {
|
||||
match data_source {
|
||||
DataSource::File { .. } | DataSource::Memory(_) => {
|
||||
Ok(Self::Decoder(Decoder::new(data_source)?))
|
||||
}
|
||||
DataSource::RawStreaming(raw) => Ok(Self::Raw(raw)),
|
||||
// It makes no sense to stream raw data which is already loaded into memory.
|
||||
_ => Err(SoundError::UnsupportedFormat),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> usize {
|
||||
match self {
|
||||
StreamingSource::Decoder(decoder) => decoder.get_sample_rate(),
|
||||
StreamingSource::Raw(raw) => raw.sample_rate(),
|
||||
StreamingSource::Null => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channel_count(&self) -> usize {
|
||||
match self {
|
||||
StreamingSource::Decoder(decoder) => decoder.get_channel_count(),
|
||||
StreamingSource::Raw(raw) => raw.channel_count(),
|
||||
StreamingSource::Null => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn channel_duration_in_samples(&self) -> usize {
|
||||
match self {
|
||||
StreamingSource::Null => 0,
|
||||
StreamingSource::Decoder(decoder) => decoder.channel_duration_in_samples(),
|
||||
StreamingSource::Raw(raw) => raw.channel_duration_in_samples(),
|
||||
}
|
||||
}
|
||||
|
||||
fn rewind(&mut self) -> Result<(), SoundError> {
|
||||
match self {
|
||||
StreamingSource::Null => Ok(()),
|
||||
StreamingSource::Decoder(decoder) => decoder.rewind(),
|
||||
StreamingSource::Raw(raw) => raw.rewind(),
|
||||
}
|
||||
}
|
||||
|
||||
fn time_seek(&mut self, location: Duration) -> Result<(), SoundError> {
|
||||
match self {
|
||||
StreamingSource::Null => Ok(()),
|
||||
StreamingSource::Decoder(decoder) => decoder.time_seek(location),
|
||||
StreamingSource::Raw(raw) => raw.time_seek(location),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read_next_samples_block_into(&mut self, buffer: &mut Vec<f32>) -> usize {
|
||||
buffer.clear();
|
||||
let count = StreamingBuffer::STREAM_SAMPLE_COUNT * self.channel_count();
|
||||
match self {
|
||||
StreamingSource::Decoder(decoder) => {
|
||||
for _ in 0..count {
|
||||
if let Some(sample) = decoder.next() {
|
||||
buffer.push(sample)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
StreamingSource::Raw(raw_streaming) => {
|
||||
for _ in 0..count {
|
||||
if let Some(sample) = raw_streaming.next() {
|
||||
buffer.push(sample)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
StreamingSource::Null => (),
|
||||
}
|
||||
|
||||
buffer.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamingBuffer {
|
||||
/// Defines amount of samples `per channel` which each streaming buffer will use for internal buffer.
|
||||
pub const STREAM_SAMPLE_COUNT: usize = 44100;
|
||||
|
||||
/// Creates new streaming buffer using given data source. May fail if data source has unsupported format
|
||||
/// or it has corrupted data. Length of internal generic buffer cannot be changed but can be fetched from
|
||||
/// `StreamingBuffer::STREAM_SAMPLE_COUNT`
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// This function will return Err if data source is `Raw`. It makes no sense to stream raw data which
|
||||
/// is already loaded into memory. Use Generic source instead!
|
||||
pub fn new(source: DataSource) -> Result<Self, SoundError> {
|
||||
let mut streaming_source = StreamingSource::new(source)?;
|
||||
|
||||
let mut samples = Vec::new();
|
||||
let channel_count = streaming_source.channel_count();
|
||||
streaming_source.read_next_samples_block_into(&mut samples);
|
||||
debug_assert_eq!(samples.len() % channel_count, 0);
|
||||
|
||||
Ok(Self {
|
||||
generic: GenericBuffer {
|
||||
samples: Samples(samples),
|
||||
sample_rate: streaming_source.sample_rate(),
|
||||
channel_count: streaming_source.channel_count(),
|
||||
channel_duration_in_samples: streaming_source.channel_duration_in_samples(),
|
||||
},
|
||||
use_count: 0,
|
||||
streaming_source,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn read_next_block(&mut self) {
|
||||
self.streaming_source
|
||||
.read_next_samples_block_into(&mut self.generic.samples);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn rewind(&mut self) -> Result<(), SoundError> {
|
||||
self.streaming_source.rewind()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn time_seek(&mut self, location: Duration) -> Result<(), SoundError> {
|
||||
self.streaming_source.time_seek(location)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StreamingBuffer {
|
||||
type Target = GenericBuffer;
|
||||
|
||||
/// Returns shared reference to internal generic buffer. Can be useful to get some info (sample rate,
|
||||
/// channel count).
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.generic
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for StreamingBuffer {
|
||||
/// Returns mutable reference to internal generic buffer. Can be used to modify it.
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.generic
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user