use std::io::{Cursor, ErrorKind};
#[cfg(not(target_family = "wasm"))]
use std::thread;
use symphonia_codec_vorbis::VorbisDecoder;
use symphonia_core::audio::SampleBuffer;
use symphonia_core::codecs::{Decoder, DecoderOptions};
use symphonia_core::errors::Error as SymphoniaError;
use symphonia_core::formats::{FormatOptions, FormatReader};
use symphonia_core::io::MediaSourceStream;
use symphonia_format_ogg::OggReader;
use crate::error::SoundFontError;
use crate::sample_header::SampleHeader;
const GUARD_FRAMES: usize = 46;
#[cfg(not(target_family = "wasm"))]
const MAX_DECODE_THREADS: usize = 8;
pub(crate) fn decode_vorbis_samples(
smpl: &[u8],
sample_headers: &mut [SampleHeader],
) -> Result<Vec<i16>, SoundFontError> {
if sample_headers.is_empty() {
return Ok(Vec::new());
}
let decoded = decode_samples(smpl, sample_headers)?;
let capacity = decoded
.iter()
.map(|samples| samples.len() + GUARD_FRAMES)
.sum();
let mut wave_data: Vec<i16> = Vec::with_capacity(capacity);
for (header, samples) in sample_headers.iter_mut().zip(decoded) {
let base = wave_data.len() as i32;
let length = samples.len() as i32;
wave_data.extend_from_slice(&samples);
wave_data.resize(wave_data.len() + GUARD_FRAMES, 0);
header.start = base;
header.end = base + length;
header.start_loop += base;
header.end_loop += base;
}
Ok(wave_data)
}
#[cfg(target_family = "wasm")]
fn decode_samples(
smpl: &[u8],
sample_headers: &[SampleHeader],
) -> Result<Vec<Vec<i16>>, SoundFontError> {
decode_samples_on_one_thread(smpl, sample_headers)
}
#[cfg(not(target_family = "wasm"))]
fn decode_samples(
smpl: &[u8],
sample_headers: &[SampleHeader],
) -> Result<Vec<Vec<i16>>, SoundFontError> {
let count = sample_headers.len();
let threads = thread::available_parallelism()
.map_or(1, |parallelism| parallelism.get())
.clamp(1, MAX_DECODE_THREADS)
.min(count);
if threads < 2 {
return decode_samples_on_one_thread(smpl, sample_headers);
}
let per_thread = count.div_ceil(threads);
thread::scope(|scope| {
let handles: Vec<_> = sample_headers
.chunks(per_thread)
.map(|chunk| {
scope.spawn(move || {
chunk
.iter()
.map(|header| decode_sample(smpl, header))
.collect::<Result<Vec<_>, _>>()
})
})
.collect();
let mut samples = Vec::with_capacity(count);
for handle in handles {
let decoded = handle.join().expect("a sample decoding thread panicked");
samples.extend(decoded?);
}
Ok(samples)
})
}
fn decode_samples_on_one_thread(
smpl: &[u8],
sample_headers: &[SampleHeader],
) -> Result<Vec<Vec<i16>>, SoundFontError> {
sample_headers
.iter()
.map(|header| decode_sample(smpl, header))
.collect()
}
fn decode_sample(smpl: &[u8], header: &SampleHeader) -> Result<Vec<i16>, SoundFontError> {
let start = header.start;
let end = header.end;
if start < 0 || end < start || end as usize > smpl.len() {
return Err(SoundFontError::SampleDecompressionFailed(format!(
"the compressed data range of sample '{}' is out of bounds ({start}..{end})",
header.name
)));
}
if header.start_loop < 0 || header.end_loop < 0 {
return Err(SoundFontError::SampleDecompressionFailed(format!(
"the loop points of sample '{}' are invalid ({}..{})",
header.name, header.start_loop, header.end_loop
)));
}
decode_stream(smpl[start as usize..end as usize].to_vec())
}
fn decode_stream(stream: Vec<u8>) -> Result<Vec<i16>, SoundFontError> {
let options = FormatOptions {
enable_gapless: true,
..Default::default()
};
let source = MediaSourceStream::new(Box::new(Cursor::new(stream)), Default::default());
let mut reader = OggReader::try_new(source, &options).map_err(decompression_error)?;
let track = reader.default_track().ok_or_else(|| {
SoundFontError::SampleDecompressionFailed(String::from(
"the sample contains no Ogg Vorbis stream",
))
})?;
let track_id = track.id;
let params = track.codec_params.clone();
match params.channels.map(|channels| channels.count()) {
Some(1) => (),
Some(count) => {
return Err(SoundFontError::SampleDecompressionFailed(format!(
"expected a mono sample, but found {count} channels"
)));
}
None => {
return Err(SoundFontError::SampleDecompressionFailed(String::from(
"expected a mono sample, but found an unknown channel layout",
)));
}
}
let mut decoder =
VorbisDecoder::try_new(¶ms, &DecoderOptions::default()).map_err(decompression_error)?;
let mut samples: Vec<i16> = Vec::new();
let mut buffer: Option<SampleBuffer<i16>> = None;
loop {
let packet = match reader.next_packet() {
Ok(packet) => packet,
Err(SymphoniaError::IoError(err)) if err.kind() == ErrorKind::UnexpectedEof => break,
Err(SymphoniaError::ResetRequired) => break,
Err(err) => return Err(decompression_error(err)),
};
if packet.track_id() != track_id {
continue;
}
let decoded = decoder.decode(&packet).map_err(decompression_error)?;
let buffer = buffer
.get_or_insert_with(|| SampleBuffer::new(decoded.capacity() as u64, *decoded.spec()));
buffer.copy_interleaved_ref(decoded);
samples.extend_from_slice(buffer.samples());
}
Ok(samples)
}
fn decompression_error(err: impl std::fmt::Display) -> SoundFontError {
SoundFontError::SampleDecompressionFailed(err.to_string())
}