use rayon::prelude::*;
#[cfg(feature = "rodio-source")]
use crate::decoded_hps_rodio_source::DecodedHpsRodioSource;
use crate::errors::HpsDecodeError;
use crate::hps::{COEFFICIENT_PAIRS_PER_CHANNEL, DSPDecoderState, Frame, Hps};
const SAMPLES_PER_FRAME: usize = 14;
#[derive(Debug, Clone, PartialEq)]
pub struct DecodedHps {
samples: Vec<i16>,
current_index: usize,
loop_sample_index: Option<usize>,
pub sample_rate: u32,
pub channel_count: u32,
}
impl Iterator for DecodedHps {
type Item = i16;
fn next(&mut self) -> Option<Self::Item> {
match (self.samples.get(self.current_index), self.loop_sample_index) {
(Some(&sample), _) => {
self.current_index += 1;
Some(sample)
}
(None, Some(loop_sample_index)) => {
self.current_index = loop_sample_index + 1;
Some(self.samples[loop_sample_index])
}
(None, None) => None,
}
}
}
impl DecodedHps {
pub(crate) fn new(hps: &Hps) -> Result<Self, HpsDecodeError> {
let samples = hps
.blocks
.par_iter()
.map(|block| {
let half_index = block.frames.len() / 2;
let left_samples = Self::decode_frames(
&block.frames[..half_index],
&block.decoder_states[0],
&hps.channel_info[0].coefficients,
)?;
let right_samples = Self::decode_frames(
&block.frames[half_index..],
&block.decoder_states[1],
&hps.channel_info[1].coefficients,
)?;
Ok(left_samples
.into_iter()
.zip(right_samples)
.flat_map(|(left_sample, right_sample)| [left_sample, right_sample]))
})
.collect::<Result<Vec<_>, HpsDecodeError>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>();
let loop_sample_index = hps.loop_block_index.map(|index| {
hps.blocks[..index]
.iter()
.map(|b| b.frames.len())
.sum::<usize>()
* SAMPLES_PER_FRAME
});
Ok(Self {
samples,
current_index: 0,
loop_sample_index,
sample_rate: hps.sample_rate,
channel_count: hps.channel_count,
})
}
fn decode_frames(
frames: &[Frame],
decoder_state: &DSPDecoderState,
coefficients: &[(i16, i16)],
) -> Result<Vec<i16>, HpsDecodeError> {
let sample_count = frames.len() * SAMPLES_PER_FRAME;
let mut samples: Vec<i16> = Vec::with_capacity(sample_count);
let mut hist1 = decoder_state.initial_hist_1;
let mut hist2 = decoder_state.initial_hist_2;
for frame in frames {
let scale = 1 << (frame.header & 0xF);
let coef_index = (frame.header >> 4) as usize;
if coef_index >= COEFFICIENT_PAIRS_PER_CHANNEL {
return Err(HpsDecodeError::InvalidCoefficientIndex(coef_index));
}
let (coef1, coef2) = coefficients[coef_index];
frame
.encoded_sample_data
.iter()
.flat_map(|&byte| [get_high_nibble(byte), get_low_nibble(byte)])
.for_each(|nibble| {
let sample = clamp_i16(
(((nibble as i32 * scale) << 11)
+ 1024
+ (coef1 as i32 * hist1 as i32 + coef2 as i32 * hist2 as i32))
>> 11,
);
hist2 = hist1;
hist1 = sample;
samples.push(sample);
});
}
Ok(samples)
}
pub fn samples(&self) -> &[i16] {
&self.samples
}
pub fn is_looping(&self) -> bool {
self.loop_sample_index.is_some()
}
pub fn duration(&self) -> std::time::Duration {
let sample_count = self.samples.len() as u64;
let samples_per_second = (self.sample_rate * self.channel_count) as u64;
std::time::Duration::from_millis(1000 * sample_count / samples_per_second)
}
#[cfg_attr(docsrs, doc(cfg(feature = "rodio-source")))]
#[cfg(feature = "rodio-source")]
pub fn into_rodio_source(self) -> DecodedHpsRodioSource {
DecodedHpsRodioSource::new(self)
}
}
static NIBBLE_TO_I8: [i8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1];
#[inline(always)]
fn get_low_nibble(byte: u8) -> i8 {
NIBBLE_TO_I8[(byte & 0xF) as usize]
}
#[inline(always)]
fn get_high_nibble(byte: u8) -> i8 {
NIBBLE_TO_I8[((byte >> 4) & 0xF) as usize]
}
#[inline(always)]
fn clamp_i16(val: i32) -> i16 {
if val < (i16::MIN as i32) {
i16::MIN
} else if val > (i16::MAX as i32) {
i16::MAX
} else {
val as i16
}
}