Skip to main content

PlaybackSlots

Struct PlaybackSlots 

Source
pub struct PlaybackSlots<T>(/* private fields */);
Expand description

Generic collection for playback slot items, e.g. Static and Flex slot data.

§Extending: Custom New Types

As this type is generic you can use custom new type structs in downstream applications / libraries, e.g. to use your own custom ‘ApplicationSlot’ type which has extra methods/fields.

use ot_tools_io::types::PlaybackSlots;
use ot_tools_io::Defaults;
use std::path::PathBuf;

#[derive(Debug, PartialEq)]
pub struct PathOnlySlotType(PathBuf);

impl Default for PathOnlySlotType {
    fn default() -> Self {
        Self(PathBuf::from("default/path/to/some/file.ext"))
    }
}

impl Defaults<Box<[Self; 128]>> for PathOnlySlotType {
    fn defaults() -> Box<[Self; 128]> where Self: Default {
        Box::new(std::array::from_fn(|_| Self::default()))
    }
}

let mut my_slots = PlaybackSlots::<PathOnlySlotType>::default();
let first = my_slots.first_mut().ok_or(())?;
*first = PathOnlySlotType(PathBuf::from("some_path"));

// first one changed
assert_eq!(my_slots[0], PathOnlySlotType(PathBuf::from("some_path")));
// rest are default
assert_eq!(my_slots[1], PathOnlySlotType::default());

§Note: Name of the type

This type should only hold elements related to static or flex slots. i.e. no recording buffer slots, ever (slot_type = flex, slot_id > 128 etc.).

Admittedly the name PlaybackSlots is somewhat of a misnomer

  • Flex slots can be recorded to
  • Sample data stored in Recording Buffers can be played back on audio tracks

However, those are special cases of Octatrack usage:

  • The designed intent of Recording Buffers is to record to the slot.
  • The designed intent of Flex Slots is to edit the audio data and playback.
  • The designed intent of Static Slots is to only playback.

§Explainer: Why does this type exist?

note: the examples in this section may not compile. they are here to demonstrate problems when using previous versions of slot data types. they are not here as examples to actually implement yourself!

Recording Buffers are Flex slots with a Slot ID > 128 under the hood (or more literally the metal case) of the Octatrack. I originally decided to represent this in this “io” library. Recording buffers are actually Flex slots. That was it. Done.

The point of the library was to load data from the binary files at the lowest granularity possible – representing how data is stored in each binary file. The thinking behind this decision was that it would allow more flexibility when using the library – access to data at the lowest granularity means the ability to do “more stuff”, right?

Having worked with the data in that format for a while – it is a massive pain in the arse dealing with the “recording buffers are actually flex slots” special case.

The library now treats Recording Buffers as a different special type of slot during Serialization/Deserialization – separate to Flex and Static slots which are considered Playback Slots.

§first example of the pain

  • simple matches need an extra case where you need to be aware of the slot_id > 128 rule.
  • users needing to be aware of some hidden logic/rule == bad.
  • my first draft of this example got the rule wrong! i used slot_id > 129! and i’m the author of this library!
match slot.slot_type {
    SlotType::Static => {}, // do something to static slots
    SlotType::Flex => {}, // do something to flex slots
    SlotType::Flex if slot.slot_id > 128 => _, // this one
}

§second example of the pain

  • our flex_slots variable contains Option<SlotAttributes> instances with the slot_type field equal to SlotType::Flex – so, only flex slot data, right?
  • not necessarily … we often still need to check whether an element returned from the collection is actually a flex playback slot and not a recording buffer slot.
  • so we have to doa manual check on the slot_id field
  • again, users needing to be aware of some hidden logic/rule == bad.
// (note: rust 2024 required for let chains)
let flex_slot_maybe = flex_slots
    .get(some_index)
    .map(|x| {
        if let Some(slot_attrs) = x
            && slot_attrs.slot_type == SlotType::Flex
            && slot_attrs.slot_id <= 128 // our "ignore recording buffers" rule
            { Some(possible_flex_slot) }
        } else {
            None
        }
    });

let actual_flex_slot = flex_slot_maybe.ok_or(SomeErr)?;

§third example of the pain

  • can’t do this with raw data – the underlying arrays are different sizes so they are different types!
  • as_array will return None for our flex slots!

pub const fn as_array<const N: usize>(&self) -> Option<&[T; N]>

If N is not exactly equal to the length of self, then this method returns None.

fn get_slot_attrs_by_type(
    project_file: &ProjectFile,
    slot_type: SlotType,
) -> Option<&[Option<SlotAttributes>; 128]> {
    match slot_type {
        SlotType::Static => project_file.slots.static_slots.as_array(),
        SlotType::Flex => project_file.slots.flex_slots.as_array(),
    }
}

// always `None` due to the size issue
assert_eq!(get_slot_attrs_for_type(&file, SlotType::Flex), None)

§possible solutions to the pain?

one possible solution is making get_slot_attrs_by_type accept a generic size argument …

fn get_slot_attrs_by_type<const N: usize>(
    project_file: &ProjectFile,
    slot_type: SlotType,
) -> &[Option<SlotAttributes>; N];

… but this creates additional problems downstream – you need to create different types for different slot types with different lengths. you then end up having to recreate this library’s “Slots” array/collection data type(s) for your own library and maintain them! not ideal for a library! our types are supposed to be re-usable!

another solution is just to give up! stick everything in a Vec with cloned data! forget references to arrays in the original file! … right?

fn get_slot_attrs_by_type(
    project_file: &ProjectFile,
    slot_type: SlotType,
) -> Vec<Option<SlotAttributes>> {
    let mut slots = vec![];

    match slot_type {
        SlotType::Static => for slot in project_file.slots.static_slots { slots.push(slot.clone()) },
        SlotType::Flex => for slot in project_file.slots.flex_slots { slots.push(slot.clone()) },
    };

    slots
}

but this means we can add more than 128 (or 136) slots to the Vec … either we check each time we push to this Vec that we haven’t exceeded the size limit (we’re manually tracking size again!) or we make this function generic – which means tracking size in types downstream again! oh, and because we have to clone() to escape the shared reference – we can have multiple owned versions of slots data which we can modify independently of each other!

great! now we can absolutely and completely bork someone’s project files for sure!

§the real solution to the pain

split recording buffers out separately with a different type – RecordingBufferSlots<T> – when we parse ProjectFiles …

so that’s what happens now. See the Slots<T> for more information.

Implementations§

Source§

impl<T> PlaybackSlots<T>

Source

pub fn id_ref(&self, id: &PlaybackSlotId) -> &T

Returns a reference to an element of the array data

§Panic-free

This method is panic-free as the id type is guaranteed to never exceed the max index of the underlying array.

Source

pub fn id_mut(&mut self, id: &PlaybackSlotId) -> &mut T

Returns a mutable reference to an element the array data

§Panic-free

This method is panic-free as the id type is guaranteed to never exceed the max index of the underlying array.

Source§

impl<T: Copy> PlaybackSlots<T>

Source

pub fn id(self, id: &PlaybackSlotId) -> T

Returns an owned element of the array data

§Panic-free

This method is panic-free as the id type is guaranteed to never exceed the max index of the underlying array.

Source§

impl<T> PlaybackSlots<T>

Source

pub fn new(value: [T; 128]) -> Self

Source

pub fn new_boxed(value: Box<[T; 128]>) -> Self

Source§

impl PlaybackSlots<Option<ActiveSlot<SlotAttributes, SlotMarkers>>>

Source

pub fn insert_or_replace_by_slot_id_field( &mut self, value: ActiveSlot<SlotAttributes, SlotMarkers>, ) -> Option<ActiveSlot<SlotAttributes, SlotMarkers>>

Insert or replace the slot in the collection based on the slot_id field in the provided ActiveSlot<SlotAttributes, SlotMarkers> instance. Will return

  • the element of the PlaybackSlots which is replaced
  • None if the slot is inserted into an empty slot
  • None if the slot_id field value is invalid
Source

pub fn take_by_id( &mut self, id: &PlaybackSlotId, ) -> Option<ActiveSlot<SlotAttributes, SlotMarkers>>

Trait Implementations§

Source§

impl<T> AsMut<PlaybackSlots<T>> for PlaybackSlots<T>

Source§

fn as_mut(&mut self) -> &mut PlaybackSlots<T>

Converts this type into a mutable reference of the (usually inferred) input type.
Source§

impl<T> AsRef<PlaybackSlots<T>> for PlaybackSlots<T>

Source§

fn as_ref(&self) -> &PlaybackSlots<T>

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl<T: Clone> Clone for PlaybackSlots<T>

Source§

fn clone(&self) -> PlaybackSlots<T>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<T: Debug> Debug for PlaybackSlots<T>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<T: Defaults<Box<[T; 128]>> + Default> Default for PlaybackSlots<T>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Default for PlaybackSlots<Option<ActiveSlot<SlotAttributes, SlotMarkers>>>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<T> Deref for PlaybackSlots<T>

Source§

type Target = [T; 128]

The resulting type after dereferencing.
Source§

fn deref(&self) -> &Self::Target

Dereferences the value.
Source§

impl<T> DerefMut for PlaybackSlots<T>

Source§

fn deref_mut(&mut self) -> &mut Self::Target

Mutably dereferences the value.
Source§

impl<'de, T: Deserialize<'de>> Deserialize<'de> for PlaybackSlots<T>

Source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl<T: Eq> Eq for PlaybackSlots<T>

Source§

impl<T: Hash> Hash for PlaybackSlots<T>

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl<T: Ord> Ord for PlaybackSlots<T>

Source§

fn cmp(&self, other: &PlaybackSlots<T>) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 (const: unstable) · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 (const: unstable) · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 (const: unstable) · Source§

fn clamp(self, min: Self, max: Self) -> Self
where Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl<T: PartialEq> PartialEq for PlaybackSlots<T>

Source§

fn eq(&self, other: &PlaybackSlots<T>) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl<T: PartialOrd> PartialOrd for PlaybackSlots<T>

Source§

fn partial_cmp(&self, other: &PlaybackSlots<T>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 (const: unstable) · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 (const: unstable) · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 (const: unstable) · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 (const: unstable) · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by the >= operator. Read more
Source§

impl<T: Serialize> Serialize for PlaybackSlots<T>

Source§

fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl<T: PartialEq> StructuralPartialEq for PlaybackSlots<T>

Auto Trait Implementations§

§

impl<T> Freeze for PlaybackSlots<T>

§

impl<T> RefUnwindSafe for PlaybackSlots<T>
where T: RefUnwindSafe,

§

impl<T> Send for PlaybackSlots<T>
where T: Send,

§

impl<T> Sync for PlaybackSlots<T>
where T: Sync,

§

impl<T> Unpin for PlaybackSlots<T>

§

impl<T> UnsafeUnpin for PlaybackSlots<T>

§

impl<T> UnwindSafe for PlaybackSlots<T>
where T: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Comparable<K> for Q
where Q: Ord + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn compare(&self, key: &K) -> Ordering

Compare self to key and return their ordering.
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<P, T> Receiver for P
where P: Deref<Target = T> + ?Sized, T: ?Sized,

Source§

type Target = T

🔬This is a nightly-only experimental API. (arbitrary_self_types)
The target type on which the method may be called.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.