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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
//! 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::FileLoadError, reflect::prelude::*, uuid::Uuid, visitor::prelude::*, TypeUuidProvider,
};
use fyrox_resource::{Resource, ResourceData, SOUND_BUFFER_RESOURCE_UUID};
use std::{
any::Any,
borrow::Cow,
fmt::Debug,
io::{Cursor, Read, Seek, SeekFrom},
ops::{Deref, DerefMut},
path::{Path, PathBuf},
time::Duration,
};
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,
/// Buffered file opened for read.
#[cfg(not(target_arch = "wasm32"))]
data: std::io::BufReader<std::fs::File>,
/// TODO: In case of WASM load file entirely.
#[cfg(target_arch = "wasm32")]
data: Cursor<Vec<u8>>,
},
/// 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.
fn rewind(&mut self) -> Result<(), SoundError> {
Ok(())
}
/// Allows you to start playback from given duration.
fn time_seek(&mut self, _duration: Duration) {}
/// Returns total duration of data. Can be `None` if internal decoder does not supports seeking.
fn duration(&self) -> Option<Duration> {
None
}
}
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) -> Result<Self, FileLoadError>
where
P: AsRef<Path>,
{
Ok(DataSource::File {
path: path.as_ref().to_path_buf(),
#[cfg(not(target_arch = "wasm32"))]
data: std::io::BufReader::new(match std::fs::File::open(path) {
Ok(file) => file,
Err(e) => return Err(FileLoadError::Io(e)),
}),
#[cfg(target_arch = "wasm32")]
data: Cursor::new(fyrox_core::io::load_file(path).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))
}
}
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!")
}
}
}
}
/// 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(FileLoadError),
}
/// Sound buffer is a data source for sound sources. See module documentation for more info.
#[derive(Debug, Visit, Reflect)]
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),
}
/// 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>, DataSource>;
/// Tries to create new generic sound buffer from a given data source.
fn new_generic(data_source: DataSource) -> Result<Resource<SoundBuffer>, DataSource>;
}
impl SoundBufferResourceExtension for SoundBufferResource {
fn new_streaming(data_source: DataSource) -> Result<Resource<SoundBuffer>, DataSource> {
Ok(Resource::new_ok(SoundBuffer::Streaming(
StreamingBuffer::new(data_source)?,
)))
}
fn new_generic(data_source: DataSource) -> Result<Resource<SoundBuffer>, DataSource> {
Ok(Resource::new_ok(SoundBuffer::Generic(GenericBuffer::new(
data_source,
)?)))
}
}
impl TypeUuidProvider for SoundBuffer {
fn type_uuid() -> Uuid {
SOUND_BUFFER_RESOURCE_UUID
}
}
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, DataSource> {
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, DataSource> {
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 path(&self) -> Cow<Path> {
Cow::from(&self.external_source_path)
}
fn set_path(&mut self, path: PathBuf) {
self.external_source_path = path;
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn type_uuid(&self) -> Uuid {
SOUND_BUFFER_RESOURCE_UUID
}
}