use alloc::string::String;
use alloc::vec;
use super::helper::*;
use crate::core::fixed::units::{ChannelVolume, Finetune, Panning, Volume};
use crate::core::sample::{LoopType, Sample, SampleDataType};
use crate::tracker::import::bin_reader::{bytes_to_trimmed_string, BinReader, ImportError};
pub const XMSAMPLE_HEADER_SIZE: usize = 40;
const MIN_TAIL_PAD: usize = 64 * 1024;
#[derive(Default, Debug)]
pub struct XmSampleHeader {
length: u32,
loop_start: u32,
loop_length: u32,
volume: u8,
finetune: i8,
flags: u8,
panning: u8,
relative_pitch: i8,
reserved: u8,
name: String,
}
impl XmSampleHeader {
fn read(r: &mut BinReader) -> Result<Self, ImportError> {
let length = r.read_u32_le()?;
let loop_start = r.read_u32_le()?;
let loop_length = r.read_u32_le()?;
let volume = r.read_u8()?;
let finetune = r.read_i8()?;
let flags = r.read_u8()?;
let panning = r.read_u8()?;
let relative_pitch = r.read_i8()?;
let reserved = r.read_u8()?;
let name_bytes: [u8; 22] = r.read_array()?;
Ok(Self {
length,
loop_start,
loop_length,
volume,
finetune,
flags,
panning,
relative_pitch,
reserved,
name: bytes_to_trimmed_string(&name_bytes),
})
}
}
#[derive(Debug, Default)]
pub struct XmSample {
header: XmSampleHeader,
data: Option<SampleDataType>,
}
impl XmSample {
pub fn load(data: &[u8]) -> Result<(&[u8], XmSample), ImportError> {
if data.len() < XMSAMPLE_HEADER_SIZE {
let mut padded = [0u8; XMSAMPLE_HEADER_SIZE];
padded[..data.len()].copy_from_slice(data);
let header = XmSampleHeader::read(&mut BinReader::new(&padded))?;
return Ok((&data[data.len()..], XmSample { header, data: None }));
}
let mut r = BinReader::new(data);
let header = XmSampleHeader::read(&mut r)?;
let xms = XmSample { header, data: None };
Ok((r.tail(), xms))
}
pub fn set_name(&mut self, name: String) {
self.header.name = name;
}
pub fn add_sample<'a>(&mut self, data: &'a [u8]) -> Result<&'a [u8], ImportError> {
let data_len: usize = self.header.length as usize;
let avail = data_len.min(data.len());
let missing = data_len - avail;
let pad = if avail == 0 || missing > avail + MIN_TAIL_PAD {
0
} else {
missing
};
let slice = &data[..avail];
let target_len = avail + pad;
let d3 = if self.header.flags & 0b0001_0000 != 0 {
let mut sample = u8_slice_to_vec_u16(slice);
sample.resize(target_len / 2, 0);
let sample2 = delta16_to_sample(sample);
SampleDataType::Mono16(sample2.into())
} else {
let mut sample = slice.to_vec();
sample.resize(target_len, 0);
let sample2 = delta8_to_sample(sample);
SampleDataType::Mono8(sample2.into())
};
self.data = Some(d3);
Ok(&data[avail..])
}
pub fn to_sample(&self) -> Sample {
let mut loop_start = self.header.loop_start;
let mut loop_length = self.header.loop_length;
if let Some(SampleDataType::Mono16(_)) = &self.data {
loop_start >>= 1;
loop_length >>= 1;
}
let sample_length = self.len();
if sample_length == 0 {
loop_start = 0;
loop_length = 0;
} else {
if loop_start >= sample_length {
loop_start = sample_length - 1;
}
if loop_length > sample_length - loop_start {
loop_length = sample_length - loop_start;
}
}
let data: SampleDataType = match &self.data {
Some(d) => d.clone(),
None => SampleDataType::Mono8(vec![].into()),
};
Sample {
name: self.header.name.clone(),
relative_pitch: self.header.relative_pitch,
finetune: Finetune::from_ratio(self.header.finetune as i32, 127),
volume: ChannelVolume::from_byte_64(self.header.volume),
default_note_volume: Volume::FULL,
panning: Panning::from_byte_255(self.header.panning),
loop_flag: match self.header.flags & 0b0000_0011 {
1 => LoopType::Forward,
2 => LoopType::PingPong,
3 => LoopType::PingPong,
_ => LoopType::No,
},
loop_start,
loop_length,
sustain_loop_flag: LoopType::No,
sustain_loop_start: 0,
sustain_loop_length: 0,
data: Some(data),
}
}
pub fn len(&self) -> u32 {
match &self.data {
Some(SampleDataType::Mono8(d)) => d.len() as u32,
Some(SampleDataType::Mono16(d)) => d.len() as u32,
_ => 0,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn name(&self) -> &str {
&self.header.name
}
pub fn data(&self) -> Option<&SampleDataType> {
self.data.as_ref()
}
pub fn loop_start_frames(&self) -> u32 {
match &self.data {
Some(SampleDataType::Mono16(_)) => self.header.loop_start >> 1,
_ => self.header.loop_start,
}
}
pub fn loop_length_frames(&self) -> u32 {
match &self.data {
Some(SampleDataType::Mono16(_)) => self.header.loop_length >> 1,
_ => self.header.loop_length,
}
}
}