use num_enum::{IntoPrimitive, TryFromPrimitive};
use serde::{Deserialize, Serialize};
use alloc::string::String;
use alloc::vec::Vec;
#[derive(
Default, Serialize, Deserialize, Copy, Clone, IntoPrimitive, TryFromPrimitive, Debug, PartialEq,
)]
#[repr(u8)]
pub enum LoopType {
#[default]
No = 0,
Forward = 1,
PingPong = 2,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum SampleDataType {
Mono8(Vec<i8>),
Mono16(Vec<i16>),
Stereo8(Vec<i8>),
Stereo16(Vec<i16>),
StereoFloat(Vec<f32>),
}
impl SampleDataType {
pub fn len(&self) -> usize {
match &self {
SampleDataType::Mono8(v) => v.len(),
SampleDataType::Mono16(v) => v.len(),
SampleDataType::Stereo8(v) => v.len() / 2,
SampleDataType::Stereo16(v) => v.len() / 2,
SampleDataType::StereoFloat(v) => v.len() / 2,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Sample {
pub name: String,
pub relative_pitch: i8,
pub finetune: f32,
pub volume: f32,
pub default_note_volume: f32,
pub panning: f32,
pub loop_flag: LoopType,
pub loop_start: u32,
pub loop_length: u32,
pub sustain_loop_flag: LoopType,
pub sustain_loop_start: u32,
pub sustain_loop_length: u32,
pub data: Option<SampleDataType>,
}
impl Sample {
pub fn len(&self) -> usize {
if let Some(d) = &self.data {
d.len()
} else {
0
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn at(&self, seek: usize) -> (f32, f32) {
match &self.data {
Some(SampleDataType::Mono8(v)) => (v[seek] as f32 / 128.0, v[seek] as f32 / 128.0),
Some(SampleDataType::Mono16(v)) => (v[seek] as f32 / 32768.0, v[seek] as f32 / 32768.0),
Some(SampleDataType::Stereo8(v)) => {
(v[seek * 2] as f32 / 128.0, v[seek * 2 + 1] as f32 / 128.0)
}
Some(SampleDataType::Stereo16(v)) => (
v[seek * 2] as f32 / 32768.0,
v[seek * 2 + 1] as f32 / 32768.0,
),
Some(SampleDataType::StereoFloat(v)) => (v[seek * 2], v[seek * 2 + 1]),
None => (0.0, 0.0),
}
}
pub fn clamp(&mut self) {
self.volume = self.volume.clamp(0.0, 1.0);
self.panning = self.panning.clamp(0.0, 1.0);
self.finetune = self.finetune.clamp(-1.0, 1.0);
self.relative_pitch = self.relative_pitch.clamp(-95, 96);
if self.sustain_loop_start as usize > self.len() {
self.sustain_loop_start = 0;
}
if self.sustain_loop_start as usize + self.sustain_loop_length as usize > self.len() {
self.sustain_loop_length = self.len() as u32 - self.sustain_loop_start;
}
if self.loop_start as usize > self.len() {
self.loop_start = 0;
}
if self.loop_start as usize + self.loop_length as usize > self.len() {
self.loop_length = self.len() as u32 - self.loop_start;
}
}
fn calculate_loop(
&self,
pos: usize,
start: usize,
length: usize,
loop_type: LoopType,
) -> usize {
if self.is_empty() {
return 0;
}
let end = start + length;
match loop_type {
LoopType::No => {
if pos < self.len() {
pos
} else {
self.len() - 1
}
}
LoopType::Forward => {
if length == 0 || pos < end {
pos.min(self.len() - 1)
} else {
start + (pos - start) % length
}
}
LoopType::PingPong => {
if length == 0 || pos < end {
pos.min(self.len() - 1)
} else {
let total_length = 2 * length;
let mod_pos = (pos - start) % total_length;
if mod_pos < length {
start + mod_pos
} else {
end - (mod_pos - length) - 1
}
}
}
}
}
pub fn seek(&self, pos: usize, sustain: bool) -> Option<usize> {
if self.is_empty() {
return None;
}
let (start, length, loop_type) = if sustain && self.sustain_loop_flag != LoopType::No {
(
self.sustain_loop_start as usize,
self.sustain_loop_length as usize,
self.sustain_loop_flag,
)
} else {
(
self.loop_start as usize,
self.loop_length as usize,
self.loop_flag,
)
};
match loop_type {
LoopType::No => {
if pos < self.len() {
Some(pos)
} else {
None
}
}
LoopType::Forward | LoopType::PingPong => {
Some(self.calculate_loop(pos, start, length, loop_type))
}
}
}
pub fn meta_seek(&self, pos: usize, sustain: bool) -> usize {
if sustain && self.sustain_loop_flag != LoopType::No {
self.calculate_loop(
pos,
self.sustain_loop_start as usize,
self.sustain_loop_length as usize,
self.sustain_loop_flag,
)
} else {
self.calculate_loop(
pos,
self.loop_start as usize,
self.loop_length as usize,
self.loop_flag,
)
}
}
pub fn bits(&self) -> u8 {
match &self.data {
Some(SampleDataType::Mono8(_)) => 8,
Some(SampleDataType::Mono16(_)) => 16,
Some(SampleDataType::Stereo8(_)) => 8,
Some(SampleDataType::Stereo16(_)) => 16,
Some(SampleDataType::StereoFloat(_)) => 32,
None => 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_sample(len: usize, loop_flag: LoopType, loop_start: u32, loop_length: u32) -> Sample {
Sample {
name: alloc::string::String::new(),
relative_pitch: 0,
finetune: 0.0,
volume: 1.0,
default_note_volume: 1.0,
panning: 0.5,
loop_flag,
loop_start,
loop_length,
sustain_loop_flag: LoopType::No,
sustain_loop_start: 0,
sustain_loop_length: 0,
data: Some(SampleDataType::Mono8(alloc::vec![0i8; len])),
}
}
#[test]
fn empty_sample_no_panic() {
let s = make_sample(0, LoopType::No, 0, 0);
assert_eq!(s.meta_seek(0, false), 0);
assert_eq!(s.meta_seek(100, false), 0);
}
#[test]
fn empty_sample_forward_loop_no_panic() {
let s = make_sample(0, LoopType::Forward, 0, 0);
assert_eq!(s.meta_seek(0, false), 0);
}
#[test]
fn empty_sample_pingpong_no_panic() {
let s = make_sample(0, LoopType::PingPong, 0, 0);
assert_eq!(s.meta_seek(0, false), 0);
}
#[test]
fn no_loop_clamps_to_end() {
let s = make_sample(10, LoopType::No, 0, 0);
assert_eq!(s.meta_seek(5, false), 5);
assert_eq!(s.meta_seek(9, false), 9);
assert_eq!(s.meta_seek(10, false), 9);
assert_eq!(s.meta_seek(100, false), 9);
}
#[test]
fn forward_loop() {
let s = make_sample(10, LoopType::Forward, 4, 4);
assert_eq!(s.meta_seek(3, false), 3);
assert_eq!(s.meta_seek(7, false), 7);
assert_eq!(s.meta_seek(8, false), 4);
assert_eq!(s.meta_seek(9, false), 5);
}
#[test]
fn pingpong_loop() {
let s = make_sample(10, LoopType::PingPong, 4, 4);
assert_eq!(s.meta_seek(3, false), 3);
assert_eq!(s.meta_seek(7, false), 7);
assert_eq!(s.meta_seek(8, false), 7);
assert_eq!(s.meta_seek(9, false), 6);
}
#[test]
fn zero_loop_length_no_panic() {
let s = make_sample(10, LoopType::Forward, 4, 0);
let _ = s.meta_seek(5, false);
}
#[test]
fn seek_empty_sample_is_none() {
let s = make_sample(0, LoopType::No, 0, 0);
assert_eq!(s.seek(0, false), None);
assert_eq!(s.seek(100, false), None);
}
#[test]
fn seek_no_loop_past_end_is_none() {
let s = make_sample(10, LoopType::No, 0, 0);
assert_eq!(s.seek(0, false), Some(0));
assert_eq!(s.seek(9, false), Some(9));
assert_eq!(s.seek(10, false), None);
assert_eq!(s.seek(100, false), None);
}
#[test]
fn seek_forward_loop_wraps() {
let s = make_sample(10, LoopType::Forward, 4, 4);
assert_eq!(s.seek(3, false), Some(3));
assert_eq!(s.seek(7, false), Some(7));
assert_eq!(s.seek(8, false), Some(4));
assert_eq!(s.seek(9, false), Some(5));
assert_eq!(s.seek(1000, false), Some(4 + (1000 - 4) % 4));
}
#[test]
fn seek_pingpong_loop_wraps() {
let s = make_sample(10, LoopType::PingPong, 4, 4);
assert_eq!(s.seek(3, false), Some(3));
assert_eq!(s.seek(7, false), Some(7));
assert_eq!(s.seek(8, false), Some(7));
assert_eq!(s.seek(9, false), Some(6));
}
#[test]
fn seek_sustain_loop_used_while_sustained() {
let mut s = make_sample(20, LoopType::No, 0, 0);
s.sustain_loop_flag = LoopType::Forward;
s.sustain_loop_start = 4;
s.sustain_loop_length = 4;
assert_eq!(s.seek(8, true), Some(4));
assert_eq!(s.seek(100, true), Some(4 + (100 - 4) % 4));
assert_eq!(s.seek(100, false), None);
}
}