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 > 128rule. - 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_slotsvariable containsOption<SlotAttributes>instances with theslot_typefield equal toSlotType::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_idfield - 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_arraywill returnNonefor our flex slots!
pub const fn as_array<const N: usize>(&self) -> Option<&[T; N]>
If
Nis not exactly equal to the length ofself, then this method returnsNone.
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>
impl<T> PlaybackSlots<T>
Sourcepub fn id_ref(&self, id: &PlaybackSlotId) -> &T
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.
Sourcepub fn id_mut(&mut self, id: &PlaybackSlotId) -> &mut T
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>
impl<T: Copy> PlaybackSlots<T>
Sourcepub fn id(self, id: &PlaybackSlotId) -> T
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>
impl<T> PlaybackSlots<T>
Source§impl PlaybackSlots<Option<ActiveSlot<SlotAttributes, SlotMarkers>>>
impl PlaybackSlots<Option<ActiveSlot<SlotAttributes, SlotMarkers>>>
Sourcepub fn insert_or_replace_by_slot_id_field(
&mut self,
value: ActiveSlot<SlotAttributes, SlotMarkers>,
) -> Option<ActiveSlot<SlotAttributes, SlotMarkers>>
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
PlaybackSlotswhich is replaced Noneif the slot is inserted into an empty slotNoneif theslot_idfield value is invalid
pub fn take_by_id( &mut self, id: &PlaybackSlotId, ) -> Option<ActiveSlot<SlotAttributes, SlotMarkers>>
Trait Implementations§
Source§impl<T> AsMut<PlaybackSlots<T>> for PlaybackSlots<T>
impl<T> AsMut<PlaybackSlots<T>> for PlaybackSlots<T>
Source§fn as_mut(&mut self) -> &mut PlaybackSlots<T>
fn as_mut(&mut self) -> &mut PlaybackSlots<T>
Source§impl<T> AsRef<PlaybackSlots<T>> for PlaybackSlots<T>
impl<T> AsRef<PlaybackSlots<T>> for PlaybackSlots<T>
Source§fn as_ref(&self) -> &PlaybackSlots<T>
fn as_ref(&self) -> &PlaybackSlots<T>
Source§impl<T: Clone> Clone for PlaybackSlots<T>
impl<T: Clone> Clone for PlaybackSlots<T>
Source§fn clone(&self) -> PlaybackSlots<T>
fn clone(&self) -> PlaybackSlots<T>
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl<T: Debug> Debug for PlaybackSlots<T>
impl<T: Debug> Debug for PlaybackSlots<T>
Source§impl Default for PlaybackSlots<Option<ActiveSlot<SlotAttributes, SlotMarkers>>>
impl Default for PlaybackSlots<Option<ActiveSlot<SlotAttributes, SlotMarkers>>>
Source§impl<T> Deref for PlaybackSlots<T>
impl<T> Deref for PlaybackSlots<T>
Source§impl<T> DerefMut for PlaybackSlots<T>
impl<T> DerefMut for PlaybackSlots<T>
Source§impl<'de, T: Deserialize<'de>> Deserialize<'de> for PlaybackSlots<T>
impl<'de, T: Deserialize<'de>> Deserialize<'de> for PlaybackSlots<T>
Source§fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
impl<T: Eq> Eq for PlaybackSlots<T>
Source§impl<T: Hash> Hash for PlaybackSlots<T>
impl<T: Hash> Hash for PlaybackSlots<T>
Source§impl<T: Ord> Ord for PlaybackSlots<T>
impl<T: Ord> Ord for PlaybackSlots<T>
Source§fn cmp(&self, other: &PlaybackSlots<T>) -> Ordering
fn cmp(&self, other: &PlaybackSlots<T>) -> Ordering
1.21.0 (const: unstable) · Source§fn max(self, other: Self) -> Selfwhere
Self: Sized,
fn max(self, other: Self) -> Selfwhere
Self: Sized,
Source§impl<T: PartialEq> PartialEq for PlaybackSlots<T>
impl<T: PartialEq> PartialEq for PlaybackSlots<T>
Source§impl<T: PartialOrd> PartialOrd for PlaybackSlots<T>
impl<T: PartialOrd> PartialOrd for PlaybackSlots<T>
Source§impl<T: Serialize> Serialize for PlaybackSlots<T>
impl<T: Serialize> Serialize for PlaybackSlots<T>
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Comparable<K> for Q
impl<Q, K> Comparable<K> for Q
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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