use crate::protocol::opcode::{
CLIP_CFG_F_FINALIZED, CLIP_CFG_F_LOOP, CLIP_CFG_F_RETAIN, CLIP_F_EDGES, CLIP_F_WHEEL,
CLIP_F_XY, CLIP_OP_PAUSE, CLIP_OP_RESTART, CLIP_OP_RESUME, CLIP_OP_START, CLIP_OP_STOP,
CLIP_OP_TOGGLE, CLIP_TAG_GAP, CLIP_TRIG_MAX, LOCK_DIR_BOTH, LOCK_DIR_NEG, LOCK_DIR_POS,
};
use crate::types::lock::blanket_from_scope;
use crate::types::{Action, Blanket, Class, LockDirection, Usage};
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Edge {
Both = LOCK_DIR_BOTH,
Press = LOCK_DIR_POS,
Release = LOCK_DIR_NEG,
}
impl Edge {
pub(crate) fn as_u8(self) -> u8 {
self as u8
}
pub(crate) fn from_u8(v: u8) -> Option<Edge> {
Some(match v {
LOCK_DIR_BOTH => Edge::Both,
LOCK_DIR_POS => Edge::Press,
LOCK_DIR_NEG => Edge::Release,
_ => return None,
})
}
}
impl From<Edge> for LockDirection {
fn from(e: Edge) -> LockDirection {
match e {
Edge::Both => LockDirection::Both,
Edge::Press => LockDirection::Positive,
Edge::Release => LockDirection::Negative,
}
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ClipAction {
Start = CLIP_OP_START,
Stop = CLIP_OP_STOP,
Pause = CLIP_OP_PAUSE,
Resume = CLIP_OP_RESUME,
Restart = CLIP_OP_RESTART,
Toggle = CLIP_OP_TOGGLE,
}
impl ClipAction {
pub(crate) fn as_u8(self) -> u8 {
self as u8
}
pub(crate) fn from_u8(v: u8) -> Option<ClipAction> {
Some(match v {
CLIP_OP_START => ClipAction::Start,
CLIP_OP_STOP => ClipAction::Stop,
CLIP_OP_PAUSE => ClipAction::Pause,
CLIP_OP_RESUME => ClipAction::Resume,
CLIP_OP_RESTART => ClipAction::Restart,
CLIP_OP_TOGGLE => ClipAction::Toggle,
_ => return None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClipTrigger {
pub on: Usage,
pub edge: Edge,
pub action: ClipAction,
pub consume: bool,
}
impl ClipTrigger {
pub fn new(on: impl Into<Usage>, edge: Edge, action: ClipAction) -> ClipTrigger {
ClipTrigger {
on: on.into(),
edge,
action,
consume: false,
}
}
pub fn consume(mut self) -> ClipTrigger {
self.consume = true;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ClipState {
#[default]
Idle,
Playing,
Paused,
Faulted,
}
impl ClipState {
pub(crate) fn from_u8(v: u8) -> Option<ClipState> {
Some(match v {
0 => ClipState::Idle,
1 => ClipState::Playing,
2 => ClipState::Paused,
3 => ClipState::Faulted,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct ClipStatus {
pub state: ClipState,
pub free: u32,
pub total: u32,
pub played: u32,
pub ticks: u32,
pub underruns: u16,
pub overruns: u16,
pub seq_gaps: u16,
pub held: Vec<Usage>,
}
impl ClipStatus {
pub fn is_held(&self, usage: impl Into<Usage>) -> bool {
let u = usage.into();
self.held.contains(&u)
}
pub(crate) fn from_payload(p: &[u8]) -> Option<ClipStatus> {
if p.len() < 25 {
return None;
}
let held = Usage::decode_list(&p[24..])?;
Some(ClipStatus {
state: ClipState::from_u8(p[1])?,
free: u32::from_le_bytes([p[2], p[3], p[4], p[5]]),
total: u32::from_le_bytes([p[6], p[7], p[8], p[9]]),
played: u32::from_le_bytes([p[10], p[11], p[12], p[13]]),
ticks: u32::from_le_bytes([p[14], p[15], p[16], p[17]]),
underruns: u16::from_le_bytes([p[18], p[19]]),
overruns: u16::from_le_bytes([p[20], p[21]]),
seq_gaps: u16::from_le_bytes([p[22], p[23]]),
held,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct ClipSettings {
pub autolock: Vec<Blanket>,
pub loop_: bool,
pub retain: bool,
pub finalized: bool,
pub triggers: Vec<ClipTrigger>,
}
impl ClipSettings {
pub(crate) fn from_payload(p: &[u8]) -> Option<ClipSettings> {
if p.len() < 25 {
return None;
}
let held_n = p[24] as usize;
let mut off = 25 + held_n * 3;
if p.len() < off + 3 {
return None;
}
let autolock = blanket_from_scope(p[off]);
let flags = p[off + 1];
let n_trig = (p[off + 2] as usize).min(CLIP_TRIG_MAX);
off += 3;
let mut triggers = Vec::with_capacity(n_trig);
for _ in 0..n_trig {
if p.len() < off + 6 {
return None;
}
if let (Some(class), Some(edge), Some(action)) = (
Class::from_u8(p[off]),
Edge::from_u8(p[off + 3]),
ClipAction::from_u8(p[off + 4]),
) {
let id = u16::from_le_bytes([p[off + 1], p[off + 2]]);
triggers.push(ClipTrigger {
on: Usage::new(class, id),
edge,
action,
consume: p[off + 5] != 0,
});
}
off += 6;
}
Some(ClipSettings {
autolock,
loop_: flags & CLIP_CFG_F_LOOP != 0,
retain: flags & CLIP_CFG_F_RETAIN != 0,
finalized: flags & CLIP_CFG_F_FINALIZED != 0,
triggers,
})
}
}
pub const CLIP_EDGES_MAX: usize = 8;
#[derive(Debug, Default, Clone)]
pub struct ClipBuilder {
bytes: Vec<u8>,
ends: Vec<usize>, }
impl ClipBuilder {
pub fn new() -> ClipBuilder {
ClipBuilder::default()
}
pub fn gap(&mut self, frames: u16) -> &mut Self {
if frames == 0 {
return self;
}
self.bytes.push(CLIP_TAG_GAP);
self.bytes.extend_from_slice(&frames.to_le_bytes());
self.ends.push(self.bytes.len());
self
}
pub fn frame(&mut self, dx: i16, dy: i16, wheel: i16, edges: &[(Usage, Action)]) -> &mut Self {
debug_assert!(
edges.len() <= CLIP_EDGES_MAX,
"clip frame: {} edges exceeds CLIP_EDGES_MAX ({CLIP_EDGES_MAX})",
edges.len()
);
let mut flags = 0u8;
if dx != 0 || dy != 0 {
flags |= CLIP_F_XY;
}
if wheel != 0 {
flags |= CLIP_F_WHEEL;
}
if !edges.is_empty() {
flags |= CLIP_F_EDGES;
}
if flags == 0 {
flags = CLIP_F_XY; }
self.bytes.push(flags);
if flags & CLIP_F_XY != 0 {
self.bytes.extend_from_slice(&dx.to_le_bytes());
self.bytes.extend_from_slice(&dy.to_le_bytes());
}
if flags & CLIP_F_WHEEL != 0 {
self.bytes.extend_from_slice(&wheel.to_le_bytes());
}
if flags & CLIP_F_EDGES != 0 {
self.bytes.push(edges.len() as u8);
for &(input, action) in edges {
let (class, id) = input.class_id();
self.bytes.push(class);
self.bytes.extend_from_slice(&id.to_le_bytes());
self.bytes.push(action.as_u8());
}
}
self.ends.push(self.bytes.len());
self
}
pub fn move_by(&mut self, dx: i16, dy: i16) -> &mut Self {
self.frame(dx, dy, 0, &[])
}
pub fn wheel(&mut self, dz: i16) -> &mut Self {
self.frame(0, 0, dz, &[])
}
pub fn edge(&mut self, input: impl Into<Usage>, action: Action) -> &mut Self {
self.frame(0, 0, 0, &[(input.into(), action)])
}
pub fn press(&mut self, usage: impl Into<Usage>) -> &mut Self {
self.edge(usage, Action::Press)
}
pub fn release(&mut self, usage: impl Into<Usage>) -> &mut Self {
self.edge(usage, Action::SoftRelease)
}
pub fn force_release(&mut self, usage: impl Into<Usage>) -> &mut Self {
self.edge(usage, Action::ForceRelease)
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn len(&self) -> usize {
self.ends.len()
}
pub fn is_empty(&self) -> bool {
self.ends.is_empty()
}
pub fn clear(&mut self) {
self.bytes.clear();
self.ends.clear();
}
pub(crate) fn entry_ends(&self) -> &[usize] {
&self.ends
}
}