use crate::projects::parse_hashmap_string_value;
use crate::projects::ProjectParseError;
use crate::settings::SlotType;
use crate::settings::{InvalidValueError, LoopMode, TimeStretchMode, TrigQuantizationMode};
use itertools::Itertools;
use ot_tools_io_derive::{AsMutDerive, AsRefDerive, IsDefaultCheck};
use serde::de::{self, Deserializer, MapAccess, Visitor};
use serde::{Deserialize, Serialize};
use serde_big_array::Array;
use std::{collections::HashMap, fmt, path::PathBuf, str::FromStr};
use thiserror::Error;
#[cfg(test)]
mod test_utils {
use crate::projects::slots::ProjectSlotsError;
use crate::projects::slots::SlotAttributes;
use crate::settings::LoopMode;
use crate::settings::SlotType;
use crate::settings::TimeStretchMode;
use crate::settings::TrigQuantizationMode;
use std::path::PathBuf;
pub(crate) fn new_slot_attr_full_args() -> Result<SlotAttributes, ProjectSlotsError> {
SlotAttributes::new(
SlotType::Static,
100,
Some(PathBuf::from("../AUDIO/location.wav")),
Some(TimeStretchMode::default()),
Some(LoopMode::default()),
Some(TrigQuantizationMode::default()),
Some(48),
Some(3360),
)
}
pub(crate) fn new_slot_attr_minimal_args() -> Result<SlotAttributes, ProjectSlotsError> {
SlotAttributes::new(SlotType::Static, 100, None, None, None, None, None, None)
}
}
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Error)]
pub enum ProjectSlotsError {
#[error("invalid slot_id ({value}), must be in range 1 <= x <= 136")]
SlotIdOutOfBounds { value: u8 },
#[error("invalid tempo ({value}), must be in range 720 <= x <= 7200")]
TempoOutOfBounds { value: u16 },
#[error("invalid gain ({value}), must be in range 24 <= x <= 120")]
GainOutOfBounds { value: u8 },
}
pub const DEFAULT_TEMPO: u16 = 2880;
pub const DEFAULT_GAIN: u8 = 48;
#[derive(
Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, AsMutDerive, AsRefDerive,
)]
pub struct SlotAttributes {
pub slot_type: SlotType,
pub slot_id: u8,
pub path: Option<PathBuf>,
pub timestrech_mode: TimeStretchMode,
pub loop_mode: LoopMode,
pub trig_quantization_mode: TrigQuantizationMode,
pub gain: u8,
pub bpm: u16,
}
#[allow(clippy::too_many_arguments)] impl SlotAttributes {
pub fn new(
slot_type: SlotType,
slot_id: u8,
path: Option<PathBuf>,
timestretch_mode: Option<TimeStretchMode>,
loop_mode: Option<LoopMode>,
trig_quantization_mode: Option<TrigQuantizationMode>,
gain: Option<u8>,
bpm: Option<u16>,
) -> Result<Self, ProjectSlotsError> {
match slot_type {
SlotType::Static => {
if !(1..=128).contains(&slot_id) {
return Err(ProjectSlotsError::SlotIdOutOfBounds { value: slot_id });
}
}
SlotType::Flex => {
if !(1..=136).contains(&slot_id) {
return Err(ProjectSlotsError::SlotIdOutOfBounds { value: slot_id });
}
}
}
if let Some(tempo) = bpm {
if !(720..=7200).contains(&tempo) {
return Err(ProjectSlotsError::TempoOutOfBounds { value: tempo });
}
}
if let Some(amp) = gain {
if !(24..=120).contains(&) {
return Err(ProjectSlotsError::GainOutOfBounds { value: amp });
}
}
Ok(Self {
slot_type,
slot_id,
path,
timestrech_mode: timestretch_mode.unwrap_or_default(),
loop_mode: loop_mode.unwrap_or_default(),
trig_quantization_mode: trig_quantization_mode.unwrap_or_default(),
gain: gain.unwrap_or(DEFAULT_GAIN),
bpm: bpm.unwrap_or(DEFAULT_TEMPO),
})
}
}
#[cfg(test)]
mod new_slot_attributes {
use crate::projects::slots::test_utils;
use crate::projects::slots::ProjectSlotsError;
use crate::projects::slots::SlotAttributes;
use crate::settings::SlotType;
#[test]
fn valid_all_args() -> Result<(), ProjectSlotsError> {
test_utils::new_slot_attr_full_args()?;
Ok(())
}
#[test]
fn valid_no_args() -> Result<(), ProjectSlotsError> {
test_utils::new_slot_attr_minimal_args()?;
Ok(())
}
#[test]
fn invalid_slot_id_too_low_static() -> Result<(), ProjectSlotsError> {
let r = SlotAttributes::new(SlotType::Static, 0, None, None, None, None, None, None);
assert!(r.is_err());
Ok(())
}
#[test]
fn invalid_slot_id_too_high_static() -> Result<(), ProjectSlotsError> {
let r = SlotAttributes::new(SlotType::Static, 129, None, None, None, None, None, None);
assert!(r.is_err());
Ok(())
}
#[test]
fn invalid_slot_id_too_high_flex() -> Result<(), ProjectSlotsError> {
let r = SlotAttributes::new(SlotType::Flex, 137, None, None, None, None, None, None);
assert!(r.is_err());
Ok(())
}
#[test]
fn invalid_gain_too_high() -> Result<(), ProjectSlotsError> {
let r = SlotAttributes::new(
SlotType::Static,
100,
None,
None,
None,
None,
Some(200),
None,
);
assert!(r.is_err());
Ok(())
}
#[test]
fn invalid_gain_too_low() -> Result<(), ProjectSlotsError> {
let r = SlotAttributes::new(SlotType::Static, 100, None, None, None, None, Some(1), None);
assert!(r.is_err());
Ok(())
}
#[test]
fn invalid_tempo_too_high() -> Result<(), ProjectSlotsError> {
let r = SlotAttributes::new(
SlotType::Static,
100,
None,
None,
None,
None,
None,
Some(8000),
);
assert!(r.is_err());
Ok(())
}
#[test]
fn invalid_tempo_too_low() -> Result<(), ProjectSlotsError> {
let r = SlotAttributes::new(
SlotType::Static,
100,
None,
None,
None,
None,
None,
Some(20),
);
assert!(r.is_err());
Ok(())
}
}
fn parse_id(hmap: &HashMap<String, String>) -> Result<u8, ProjectParseError> {
let x = parse_hashmap_string_value::<u8>(hmap, "slot", None)?;
Ok(x)
}
fn parse_loop_mode(hmap: &HashMap<String, String>) -> Result<LoopMode, InvalidValueError> {
let default = LoopMode::default() as u8;
let default_str = format!["{default}"];
let x = parse_hashmap_string_value::<u8>(hmap, "loopmode", Some(default_str.as_str()))
.unwrap_or(default);
LoopMode::try_from(&x)
}
fn parse_tstrech_mode(
hmap: &HashMap<String, String>,
) -> Result<TimeStretchMode, InvalidValueError> {
let default = TimeStretchMode::default() as u8;
let default_str = format!["{default}"];
let x = parse_hashmap_string_value::<u8>(hmap, "tsmode", Some(default_str.as_str()))
.unwrap_or(default);
TimeStretchMode::try_from(&x)
}
fn parse_trig_quantize_mode(
hmap: &HashMap<String, String>,
) -> Result<TrigQuantizationMode, InvalidValueError> {
let default = TrigQuantizationMode::default() as u8;
let default_str = format!["{default}"];
let x = parse_hashmap_string_value::<u8>(hmap, "trigquantization", Some(default_str.as_str()))
.unwrap_or(default);
TrigQuantizationMode::try_from(x)
}
fn parse_gain(hmap: &HashMap<String, String>) -> Result<u8, ProjectParseError> {
let x =
parse_hashmap_string_value::<u8>(hmap, "gain", Some(format!["{DEFAULT_GAIN}"].as_str()))
.unwrap_or(DEFAULT_GAIN);
Ok(x)
}
fn parse_tempo(hmap: &HashMap<String, String>) -> Result<u16, ProjectParseError> {
let x = parse_hashmap_string_value::<u16>(
hmap,
"bpmx24",
Some(format!["{DEFAULT_TEMPO}"].as_str()),
)
.unwrap_or(DEFAULT_TEMPO);
Ok(x)
}
fn parse_path(hmap: &HashMap<String, String>) -> Result<PathBuf, ProjectParseError> {
let path_str = hmap.get("path").ok_or(ProjectParseError::HashMap)?;
let path = PathBuf::from_str(path_str).map_err(|_| ProjectParseError::String)?;
Ok(path)
}
impl TryFrom<&HashMap<String, String>> for SlotAttributes {
type Error = ProjectParseError;
fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
let slot_id = parse_id(value)?;
let sample_slot_type = value
.get("type")
.ok_or(ProjectParseError::HashMap)?
.to_string();
let slot_type = SlotType::try_from(sample_slot_type)?;
let path = parse_path(value)?;
let loop_mode = parse_loop_mode(value)?;
let timestrech_mode = parse_tstrech_mode(value)?;
let trig_quantization_mode = parse_trig_quantize_mode(value)?;
let gain = parse_gain(value)?;
let bpm = parse_tempo(value)?;
let sample_struct = Self {
slot_type,
slot_id,
path: if path.as_os_str() != "" {
Some(path)
} else {
None
},
timestrech_mode,
loop_mode,
trig_quantization_mode,
gain,
bpm,
};
Ok(sample_struct)
}
}
impl FromStr for SlotAttributes {
type Err = ProjectParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let k_v: Vec<Vec<&str>> = s
.strip_prefix("\r\n\r\n[SAMPLE]\r\n")
.ok_or(ProjectParseError::HashMap)?
.strip_suffix("\r\n")
.ok_or(ProjectParseError::HashMap)?
.split("\r\n")
.map(|x: &str| x.split('=').collect_vec())
.filter(|x: &Vec<&str>| x.len() == 2)
.collect_vec();
let mut hmap: HashMap<String, String> = HashMap::new();
for key_value_pair in k_v {
hmap.insert(
key_value_pair[0].to_string().to_lowercase(),
key_value_pair[1].to_string(),
);
}
let sample_struct = SlotAttributes::try_from(&hmap)?;
Ok(sample_struct)
}
}
impl fmt::Display for SlotAttributes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let mut s = "[SAMPLE]\r\n".to_string();
s.push_str(&format!("TYPE={}", self.slot_type));
s.push_str("\r\n");
s.push_str(format!("SLOT={:0>3}", self.slot_id).as_str());
s.push_str("\r\n");
if let Some(path) = &self.path {
s.push_str(
format!("PATH={path:#?}")
.replace('"', "") .replace("\\", "") .as_str(),
);
} else {
s.push_str("PATH=");
}
s.push_str("\r\n");
s.push_str(format!("BPMx24={}", self.bpm).as_str());
s.push_str("\r\n");
s.push_str(format!("TSMODE={}", self.timestrech_mode as u8).as_str());
s.push_str("\r\n");
s.push_str(format!("LOOPMODE={}", self.loop_mode as u8).as_str());
s.push_str("\r\n");
s.push_str(format!("GAIN={}", self.gain).as_str());
s.push_str("\r\n");
s.push_str(format!("TRIGQUANTIZATION={}", self.trig_quantization_mode as u8).as_str());
s.push_str("\r\n[/SAMPLE]");
write!(f, "{s:#}")
}
}
#[cfg(test)]
mod test_slot_attr_display {
use crate::projects::slots::test_utils;
use crate::projects::slots::ProjectSlotsError;
#[test]
fn valid_full() -> Result<(), ProjectSlotsError> {
test_utils::new_slot_attr_full_args()?.to_string();
Ok(())
}
#[test]
fn valid_minimal() -> Result<(), ProjectSlotsError> {
test_utils::new_slot_attr_minimal_args()?.to_string();
Ok(())
}
}
impl<'de> Deserialize<'de> for SlotAttributes {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
enum Field {
SlotType,
SlotId,
Path,
Timestretch,
Loop,
Quant,
Gain,
Bpm,
}
const FIELDS: &[&str] = &[
"slot_type",
"slot_id",
"path",
"timestrech_mode",
"loop_mode",
"trig_quantization_mode",
"gain",
"bpm",
];
impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;
impl Visitor<'_> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(
FIELDS
.iter()
.map(|x| format!["`{x}`"])
.collect::<Vec<_>>()
.join(" or ")
.as_str(),
)
}
fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
E: de::Error,
{
match value {
"slot_type" => Ok(Field::SlotType),
"slot_id" => Ok(Field::SlotId),
"path" => Ok(Field::Path),
"timestrech_mode" => Ok(Field::Timestretch),
"loop_mode" => Ok(Field::Loop),
"trig_quantization_mode" => Ok(Field::Quant),
"gain" => Ok(Field::Gain),
"bpm" => Ok(Field::Bpm),
_ => Err(de::Error::unknown_field(value, FIELDS)),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
struct SlotAttributesVisitor;
impl<'de> Visitor<'de> for SlotAttributesVisitor {
type Value = SlotAttributes;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct SlotAttributes")
}
fn visit_map<V>(self, mut map: V) -> Result<SlotAttributes, V::Error>
where
V: MapAccess<'de>,
{
let mut slot_type = None;
let mut slot_id = None;
let mut path = None;
let mut timestretch_mode = None;
let mut loop_mode = None;
let mut trig_quantization_mode = None;
let mut gain = None;
let mut bpm = None;
while let Some(key) = map.next_key()? {
match key {
Field::SlotType => {
if slot_type.is_some() {
return Err(de::Error::duplicate_field("slot_type"));
}
slot_type = Some(map.next_value::<SlotType>()?);
}
Field::SlotId => {
if slot_id.is_some() {
return Err(de::Error::duplicate_field("slot_id"));
}
slot_id = Some(map.next_value::<u8>()?);
}
Field::Path => {
if path.is_some() {
return Err(de::Error::duplicate_field("path"));
}
path = Some(map.next_value::<PathBuf>()?);
}
Field::Timestretch => {
if timestretch_mode.is_some() {
return Err(de::Error::duplicate_field("timestretch_mode"));
}
timestretch_mode = Some(map.next_value::<TimeStretchMode>()?);
}
Field::Loop => {
if loop_mode.is_some() {
return Err(de::Error::duplicate_field("loop_mode"));
}
loop_mode = Some(map.next_value::<LoopMode>()?);
}
Field::Quant => {
if trig_quantization_mode.is_some() {
return Err(de::Error::duplicate_field("trig_quantization_mode"));
}
trig_quantization_mode =
Some(map.next_value::<TrigQuantizationMode>()?);
}
Field::Gain => {
if gain.is_some() {
return Err(de::Error::duplicate_field("gain"));
}
gain = Some(map.next_value::<u8>()?);
}
Field::Bpm => {
if bpm.is_some() {
return Err(de::Error::duplicate_field("bpm"));
}
bpm = Some(map.next_value::<u16>()?);
}
}
}
let slot = SlotAttributes {
slot_type: slot_type.ok_or_else(|| de::Error::missing_field("slot_type"))?,
slot_id: slot_id.ok_or_else(|| de::Error::missing_field("slot_type"))?,
path, timestrech_mode: timestretch_mode
.ok_or_else(|| de::Error::missing_field("trimstretch_mode"))?,
loop_mode: loop_mode.ok_or_else(|| de::Error::missing_field("loop_mode"))?,
trig_quantization_mode: trig_quantization_mode
.ok_or_else(|| de::Error::missing_field("trig_quantization_mode"))?,
gain: gain.ok_or_else(|| de::Error::missing_field("gain"))?,
bpm: bpm.ok_or_else(|| de::Error::missing_field("bpm"))?,
};
Ok(slot)
}
}
deserializer.deserialize_struct("SampleSlot", FIELDS, SlotAttributesVisitor)
}
}
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
AsMutDerive,
AsRefDerive,
IsDefaultCheck,
)]
pub struct SlotsAttributes {
pub static_slots: Array<Option<SlotAttributes>, 128>,
pub flex_slots: Array<Option<SlotAttributes>, 136>,
}
impl<'de> Deserialize<'de> for SlotsAttributes {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
enum Field {
Static,
Flex,
}
impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;
impl Visitor<'_> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`static_slots` or `flex_slots`")
}
fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
E: de::Error,
{
match value {
"static_slots" => Ok(Field::Static),
"flex_slots" => Ok(Field::Flex),
_ => Err(de::Error::unknown_field(value, FIELDS)),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
struct SampleSlotsVisitor;
impl<'de> Visitor<'de> for SampleSlotsVisitor {
type Value = SlotsAttributes;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct SampleSlots")
}
fn visit_unit<E>(self) -> Result<SlotsAttributes, E> {
Ok(SlotsAttributes::default())
}
fn visit_map<V>(self, mut map: V) -> Result<SlotsAttributes, V::Error>
where
V: MapAccess<'de>,
{
let mut static_slots = None;
let mut flex_slots = None;
while let Some(key) = map.next_key()? {
match key {
Field::Static => {
if static_slots.is_some() {
return Err(de::Error::duplicate_field("static_slots"));
}
static_slots =
Some(map.next_value::<Array<Option<SlotAttributes>, 128>>()?);
}
Field::Flex => {
if flex_slots.is_some() {
return Err(de::Error::duplicate_field("flex_slots"));
}
flex_slots =
Some(map.next_value::<Array<Option<SlotAttributes>, 136>>()?);
}
}
}
let s_slots =
static_slots.ok_or_else(|| de::Error::missing_field("static_slots"))?;
let f_slots = flex_slots.ok_or_else(|| de::Error::missing_field("flex_slots"))?;
let slots = SlotsAttributes {
static_slots: s_slots,
flex_slots: f_slots,
};
Ok(slots)
}
}
const FIELDS: &[&str] = &["static_slots", "flex_slots"];
deserializer.deserialize_struct("SampleSlots", FIELDS, SampleSlotsVisitor)
}
}
fn flex_slot_default_case_switch(i: usize) -> Option<SlotAttributes> {
if i <= 127 {
None
} else {
Some(SlotAttributes {
slot_type: SlotType::Flex,
slot_id: i as u8 + 1,
path: None,
timestrech_mode: TimeStretchMode::default(),
loop_mode: LoopMode::default(),
trig_quantization_mode: TrigQuantizationMode::default(),
gain: 72, bpm: DEFAULT_TEMPO,
})
}
}
impl Default for SlotsAttributes {
fn default() -> Self {
Self {
static_slots: Array(std::array::from_fn(|_| None)),
flex_slots: Array(std::array::from_fn(flex_slot_default_case_switch)),
}
}
}
impl FromStr for SlotsAttributes {
type Err = ProjectParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let footer_stripped = s
.strip_suffix("\r\n\r\n############################\r\n\r\n")
.ok_or(ProjectParseError::Footer)?;
let data_window: Vec<&str> = footer_stripped
.split("############################\r\n# Samples\r\n############################")
.collect();
let mut samples_string: Vec<&str> = data_window[1].split("[/SAMPLE]").collect();
samples_string.pop();
let mut slots = Self::default();
for s in &samples_string {
let slot = SlotAttributes::from_str(s)?;
let zero_indexed_id = slot.slot_id as usize - 1;
match slot.slot_type {
SlotType::Static => {
slots.static_slots[zero_indexed_id] = Some(slot);
}
SlotType::Flex => {
slots.flex_slots[zero_indexed_id] = Some(slot);
}
}
}
Ok(slots)
}
}
impl fmt::Display for SlotsAttributes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let mut string_slots: String = "".to_string();
let slots = vec![self.static_slots.to_vec(), self.flex_slots.to_vec()];
let slots_concat = itertools::concat(slots).into_iter().flatten();
for slot in slots_concat {
string_slots.push_str(&slot.to_string());
string_slots.push_str("\r\n\r\n");
}
string_slots = string_slots
.strip_suffix("\r\n\r\n")
.ok_or(fmt::Error)?
.to_string();
write!(f, "{string_slots:#}")
}
}
#[cfg(test)]
#[allow(unused_imports)]
mod test {
#[test]
fn parse_id_001_correct() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("slot".to_string(), "001".to_string());
let slot_id = crate::projects::slots::parse_id(&hmap);
assert_eq!(1, slot_id.unwrap());
}
#[test]
fn parse_id_1_correct() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("slot".to_string(), "1".to_string());
let slot_id = crate::projects::slots::parse_id(&hmap);
assert_eq!(1, slot_id.unwrap());
}
#[test]
fn parse_id_127_correct() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("slot".to_string(), "127".to_string());
let slot_id = crate::projects::slots::parse_id(&hmap);
assert_eq!(127, slot_id.unwrap());
}
#[test]
fn parse_id_099_correct() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("slot".to_string(), "099".to_string());
let slot_id = crate::projects::slots::parse_id(&hmap);
assert_eq!(99, slot_id.unwrap());
}
#[test]
fn parse_id_010_correct() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("slot".to_string(), "010".to_string());
let slot_id = crate::projects::slots::parse_id(&hmap);
assert_eq!(10, slot_id.unwrap());
}
#[test]
fn test_parse_id_err_bad_value_type_err() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("slot".to_string(), "AAAA".to_string());
let slot_id = crate::projects::slots::parse_id(&hmap);
assert!(slot_id.is_err());
}
#[test]
fn test_parse_tempo_correct_default() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("bpmx24".to_string(), "2880".to_string());
let r = crate::projects::slots::parse_tempo(&hmap);
assert_eq!(2880_u16, r.unwrap());
}
#[test]
fn test_parse_tempo_correct_min() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("bpmx24".to_string(), "720".to_string());
let r = crate::projects::slots::parse_tempo(&hmap);
assert_eq!(720_u16, r.unwrap());
}
#[test]
fn test_parse_tempo_correct_max() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("bpmx24".to_string(), "7200".to_string());
let r = crate::projects::slots::parse_tempo(&hmap);
assert_eq!(7200_u16, r.unwrap());
}
#[test]
fn test_parse_tempo_bad_value_type_default_return() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("bpmx24".to_string(), "AAAFSFSFSSFfssafAA".to_string());
let r = crate::projects::slots::parse_tempo(&hmap);
assert_eq!(r.unwrap(), 2880_u16);
}
#[test]
fn test_parse_gain_correct() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("gain".to_string(), "72".to_string());
let r = crate::projects::slots::parse_gain(&hmap);
assert_eq!(72, r.unwrap());
}
#[test]
fn test_parse_gain_bad_value_type_default_return() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("gain".to_string(), "AAAFSFSFSSFfssafAA".to_string());
let r = crate::projects::slots::parse_gain(&hmap);
assert_eq!(r.unwrap(), super::DEFAULT_GAIN); }
#[test]
fn test_parse_loop_mode_correct_off() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("loopmode".to_string(), "0".to_string());
let r = crate::projects::slots::parse_loop_mode(&hmap);
assert_eq!(r.unwrap(), crate::settings::LoopMode::Off);
}
#[test]
fn test_parse_loop_mode_correct_normal() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("loopmode".to_string(), "1".to_string());
let r = crate::projects::slots::parse_loop_mode(&hmap);
assert_eq!(r.unwrap(), crate::settings::LoopMode::Normal);
}
#[test]
fn test_parse_loop_mode_correct_pingpong() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("loopmode".to_string(), "2".to_string());
let r = crate::projects::slots::parse_loop_mode(&hmap);
assert_eq!(r.unwrap(), crate::settings::LoopMode::PingPong);
}
#[test]
fn test_parse_loop_mode_bad_value_type_default_return() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("loopmode".to_string(), "AAAFSFSFSSFfssafAA".to_string());
let r = crate::projects::slots::parse_loop_mode(&hmap);
assert_eq!(r.unwrap(), crate::settings::LoopMode::default());
}
#[test]
fn test_parse_tstretch_correct_off() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("tsmode".to_string(), "0".to_string());
let r = crate::projects::slots::parse_tstrech_mode(&hmap);
assert_eq!(crate::settings::TimeStretchMode::Off, r.unwrap());
}
#[test]
fn test_parse_tstretch_correct_normal() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("tsmode".to_string(), "2".to_string());
let r = crate::projects::slots::parse_tstrech_mode(&hmap);
assert_eq!(crate::settings::TimeStretchMode::Normal, r.unwrap());
}
#[test]
fn test_parse_tstretch_correct_beat() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("tsmode".to_string(), "3".to_string());
let r = crate::projects::slots::parse_tstrech_mode(&hmap);
assert_eq!(crate::settings::TimeStretchMode::Beat, r.unwrap());
}
#[test]
fn test_parse_tstretch_bad_value_type_default_return() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("tsmode".to_string(), "AAAFSFSFSSFfssafAA".to_string());
let r = crate::projects::slots::parse_tstrech_mode(&hmap);
assert_eq!(r.unwrap(), crate::settings::TimeStretchMode::default());
}
#[test]
fn test_parse_tquantize_correct_off() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("trigquantization".to_string(), "255".to_string());
let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
assert_eq!(crate::settings::TrigQuantizationMode::Direct, r.unwrap());
}
#[test]
fn test_parse_tquantize_correct_direct() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("trigquantization".to_string(), "0".to_string());
let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
assert_eq!(
crate::settings::TrigQuantizationMode::PatternLength,
r.unwrap()
);
}
#[test]
fn test_parse_tquantize_correct_onestep() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("trigquantization".to_string(), "1".to_string());
let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
assert_eq!(crate::settings::TrigQuantizationMode::OneStep, r.unwrap());
}
#[test]
fn test_parse_tquantize_correct_twostep() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("trigquantization".to_string(), "2".to_string());
let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
assert_eq!(crate::settings::TrigQuantizationMode::TwoSteps, r.unwrap());
}
#[test]
fn test_parse_tquantize_correct_threestep() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("trigquantization".to_string(), "3".to_string());
let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
assert_eq!(
crate::settings::TrigQuantizationMode::ThreeSteps,
r.unwrap()
);
}
#[test]
fn test_parse_tquantize_correct_fourstep() {
let mut hmap = std::collections::HashMap::new();
hmap.insert("trigquantization".to_string(), "4".to_string());
let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
assert_eq!(crate::settings::TrigQuantizationMode::FourSteps, r.unwrap());
}
#[test]
fn test_parse_tquantize_bad_value_type_default_return() {
let mut hmap = std::collections::HashMap::new();
hmap.insert(
"trigquantization".to_string(),
"AAAFSFSFSSFfssafAA".to_string(),
);
let r = crate::projects::slots::parse_trig_quantize_mode(&hmap);
assert_eq!(r.unwrap(), crate::settings::TrigQuantizationMode::default());
}
use std::path::PathBuf;
#[test]
fn test_parse_path_good_utf8_value() {
let test_path = "../AUDIO/some/file.wav";
let mut hmap = std::collections::HashMap::new();
hmap.insert("path".to_string(), test_path.to_string());
let r = crate::projects::slots::parse_path(&hmap);
assert_eq!(r.unwrap(), PathBuf::from(test_path));
}
#[test]
fn test_parse_empty_path() {
let test_path = "";
let mut hmap = std::collections::HashMap::new();
hmap.insert("path".to_string(), test_path.to_string());
let r = crate::projects::slots::parse_path(&hmap);
assert_eq!(r.unwrap(), PathBuf::from(test_path));
}
#[test]
fn test_parse_non_utf8_path() {
let test_path = "../AUDIO/🇯🇲something.wav";
let mut hmap = std::collections::HashMap::new();
hmap.insert("path".to_string(), test_path.to_string());
let r = crate::projects::slots::parse_path(&hmap);
assert_eq!(r.unwrap(), PathBuf::from(test_path));
}
}