mod deserialize;
mod parsing;
mod serialize;
use crate::generics::{PlaybackSlots, RecordingBufferSlots, Slots};
use crate::settings::{LoopMode, SlotType, TimeStretchMode, TrigQuantizationMode};
use crate::OtToolsIoError;
use ot_tools_io_derive::{AsMutDerive, AsRefDerive};
use serde::Serialize;
use std::array::from_fn;
use std::path::PathBuf;
use thiserror::Error;
#[cfg(test)]
mod test_utils {
use crate::projects::slots::SlotAttributes;
use crate::settings::LoopMode;
use crate::settings::SlotType;
use crate::settings::TimeStretchMode;
use crate::settings::TrigQuantizationMode;
use crate::OtToolsIoError;
use std::path::PathBuf;
pub(crate) fn new_slot_attr_full_args() -> Result<SlotAttributes, OtToolsIoError> {
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, OtToolsIoError> {
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 <= 128")]
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 },
#[error("failed to parse sample settings data")]
SettingsRead,
#[error("failed to canonicalize slot path ")]
SlotPathCanon(#[from] std::io::Error),
}
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, OtToolsIoError> {
match slot_type {
SlotType::Static => {
if !(1..=128).contains(&slot_id) {
return Err(ProjectSlotsError::SlotIdOutOfBounds { value: slot_id }.into());
}
}
SlotType::Flex => {
if !(1..=128).contains(&slot_id) {
return Err(ProjectSlotsError::SlotIdOutOfBounds { value: slot_id }.into());
}
}
}
if let Some(tempo) = bpm {
if !(720..=7200).contains(&tempo) {
return Err(ProjectSlotsError::TempoOutOfBounds { value: tempo }.into());
}
}
if let Some(amp) = gain {
if !(24..=120).contains(&) {
return Err(ProjectSlotsError::GainOutOfBounds { value: amp }.into());
}
}
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),
})
}
pub fn new_no_opts(
slot_type: SlotType,
slot_id: u8,
path: Option<PathBuf>,
) -> Result<Self, OtToolsIoError> {
SlotAttributes::new(slot_type, slot_id, path, None, None, None, None, None)
}
}
#[cfg(test)]
mod new_slot_attributes {
use crate::projects::slots::test_utils;
use crate::projects::slots::SlotAttributes;
use crate::settings::SlotType;
use crate::OtToolsIoError;
#[test]
fn valid_all_args() -> Result<(), OtToolsIoError> {
test_utils::new_slot_attr_full_args()?;
Ok(())
}
#[test]
fn valid_no_args() -> Result<(), OtToolsIoError> {
test_utils::new_slot_attr_minimal_args()?;
Ok(())
}
#[test]
fn invalid_slot_id_too_low_static() -> Result<(), OtToolsIoError> {
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<(), OtToolsIoError> {
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<(), OtToolsIoError> {
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<(), OtToolsIoError> {
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<(), OtToolsIoError> {
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<(), OtToolsIoError> {
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<(), OtToolsIoError> {
let r = SlotAttributes::new(
SlotType::Static,
100,
None,
None,
None,
None,
None,
Some(20),
);
assert!(r.is_err());
Ok(())
}
}
fn recording_buffer_defaults(i: usize) -> Option<SlotAttributes> {
Some(SlotAttributes {
slot_type: SlotType::Flex,
slot_id: i as u8 + 129,
path: None,
timestrech_mode: TimeStretchMode::default(),
loop_mode: LoopMode::default(),
trig_quantization_mode: TrigQuantizationMode::default(),
gain: 72, bpm: DEFAULT_TEMPO,
})
}
impl Default for Slots<Option<SlotAttributes>> {
fn default() -> Self {
Self {
static_slots: PlaybackSlots::<Option<SlotAttributes>>::new(from_fn(|_| None)),
flex_slots: PlaybackSlots::<Option<SlotAttributes>>::new(from_fn(|_| None)),
recording_buffers: RecordingBufferSlots::<Option<SlotAttributes>>::new(from_fn(
recording_buffer_defaults,
)),
}
}
}