ot-tools-io 0.11.3

A library crate for reading/writing binary data files used by the Elektron Octatrack DPS-1.
Documentation
/*
SPDX-License-Identifier: GPL-3.0-or-later
Copyright © 2026 Mike Robeson [dijksterhuis]
*/

use crate::generics::{PlaybackSlots, RecordingBufferSlots, Slots};
use crate::projects::SlotAttributes;
use crate::settings::{LoopMode, SlotType, TimeStretchMode, TrigQuantizationMode};
use serde::{
    de::{self, MapAccess, Visitor},
    Deserialize, Deserializer,
};
use std::fmt;
use std::path::PathBuf;

// YAML/JSON Deserialization for Sample Slot
// we can get away with just defining map deserialization for YAML/JSON.
// `ToString`/`FromStr` implementations are currently used for the actual data file.
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,
        }

        // TODO: FIELDS_MAP: Tuple array
        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, // allowed to be missing to handle recording buffer empty paths
                    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)
    }
}

// YAML/JSON Deserialization for Sample Slots
// we can get away with just defining map deserialization for YAML/JSON.
// `ToString`/`FromStr` implementations are currently used for the actual data file.
impl<'de> Deserialize<'de> for Slots<Option<SlotAttributes>> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        enum Field {
            Static,
            Flex,
            Recording,
        }

        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` or `recording_buffers`")
                    }

                    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),
                            "recording_buffers" => Ok(Field::Recording),
                            _ => Err(de::Error::unknown_field(value, FIELDS)),
                        }
                    }
                }

                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        struct SampleSlotsVisitor;

        impl<'de> Visitor<'de> for SampleSlotsVisitor {
            type Value = Slots<Option<SlotAttributes>>;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct Slots<Option<SlotAttributes>>")
            }

            fn visit_unit<E>(self) -> Result<Slots<Option<SlotAttributes>>, E> {
                Ok(Slots::<Option<SlotAttributes>>::default())
            }

            fn visit_map<V>(self, mut map: V) -> Result<Slots<Option<SlotAttributes>>, V::Error>
            where
                V: MapAccess<'de>,
            {
                let mut s_slots = None;
                let mut f_slots = None;
                let mut r_slots = None;

                while let Some(key) = map.next_key()? {
                    match key {
                        Field::Static => {
                            if s_slots.is_some() {
                                return Err(de::Error::duplicate_field("static_slots"));
                            }
                            s_slots =
                                Some(map.next_value::<PlaybackSlots<Option<SlotAttributes>>>()?);
                        }
                        Field::Flex => {
                            if f_slots.is_some() {
                                return Err(de::Error::duplicate_field("flex_slots"));
                            }
                            f_slots =
                                Some(map.next_value::<PlaybackSlots<Option<SlotAttributes>>>()?);
                        }
                        Field::Recording => {
                            if r_slots.is_some() {
                                return Err(de::Error::duplicate_field("recording_buffers"));
                            }
                            r_slots = Some(
                                map.next_value::<RecordingBufferSlots<Option<SlotAttributes>>>()?,
                            );
                        }
                    }
                }

                let static_slots =
                    s_slots.ok_or_else(|| de::Error::missing_field("static_slots"))?;

                let flex_slots = f_slots.ok_or_else(|| de::Error::missing_field("flex_slots"))?;

                let recording_buffers =
                    r_slots.ok_or_else(|| de::Error::missing_field("recording_buffers"))?;

                Ok(Slots::new(flex_slots, recording_buffers, static_slots))
            }
        }

        const FIELDS: &[&str] = &["static_slots", "flex_slots", "recording_buffers"];
        deserializer.deserialize_struct("SampleSlots", FIELDS, SampleSlotsVisitor)
    }
}