use crate::core::fixed::fixed::{Q15, Q8_8};
use crate::core::fixed::units::PitchDelta;
use crate::core::pitch::Pitch;
use crate::core::waveform::Waveform;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AutomationTarget {
GlobalVolume,
Bpm,
Speed,
ChannelVolume(u8),
ChannelPanning(u8),
TrackVolume(u32),
TrackPanning(u32),
TrackPitch(u32),
TrackChannelVolume(u32),
DeviceParam { track: u32, device: u16, param: u16 },
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum AutomationValue {
Normalized(Q15),
Bpm(u16),
Speed(u8),
Pitch(PitchDelta),
}
impl AutomationValue {
pub fn is_compatible_with(&self, target: AutomationTarget) -> bool {
matches!(
(self, target),
(
AutomationValue::Normalized(_),
AutomationTarget::GlobalVolume
) | (
AutomationValue::Normalized(_),
AutomationTarget::ChannelVolume(_)
) | (
AutomationValue::Normalized(_),
AutomationTarget::ChannelPanning(_)
) | (
AutomationValue::Normalized(_),
AutomationTarget::TrackVolume(_)
) | (
AutomationValue::Normalized(_),
AutomationTarget::TrackPanning(_)
) | (
AutomationValue::Normalized(_),
AutomationTarget::TrackPitch(_)
) | (
AutomationValue::Normalized(_),
AutomationTarget::TrackChannelVolume(_)
) | (AutomationValue::Pitch(_), AutomationTarget::TrackPitch(_))
| (
AutomationValue::Normalized(_),
AutomationTarget::DeviceParam { .. }
)
| (AutomationValue::Bpm(_), AutomationTarget::Bpm)
| (AutomationValue::Speed(_), AutomationTarget::Speed)
)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub struct AutomationPoint {
pub tick: u32,
pub value: AutomationValue,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum LfoEvent {
Set {
tick: u32,
speed: Q8_8,
depth: Q15,
waveform: Waveform,
retrig: bool,
},
DepthOnly { tick: u32, depth: Q15 },
SpeedOnly { tick: u32, speed: Q8_8 },
WaveformOnly {
tick: u32,
waveform: Waveform,
retrig: bool,
},
Clear { tick: u32 },
}
impl LfoEvent {
#[inline]
pub fn tick(&self) -> u32 {
match *self {
Self::Set { tick, .. } => tick,
Self::DepthOnly { tick, .. } => tick,
Self::SpeedOnly { tick, .. } => tick,
Self::WaveformOnly { tick, .. } => tick,
Self::Clear { tick } => tick,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum SlideEvent {
Set { tick: u32, rate: Q15, fine: bool },
Clear { tick: u32 },
}
impl SlideEvent {
#[inline]
pub fn tick(&self) -> u32 {
match *self {
Self::Set { tick, .. } => tick,
Self::Clear { tick } => tick,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum GlideEvent {
Set { tick: u32, target: Pitch, rate: i16 },
Clear { tick: u32 },
}
impl GlideEvent {
#[inline]
pub fn tick(&self) -> u32 {
match *self {
Self::Set { tick, .. } => tick,
Self::Clear { tick } => tick,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum LaneKind {
Points(Vec<AutomationPoint>),
Lfo { events: Vec<LfoEvent> },
Slide { events: Vec<SlideEvent> },
Glide { events: Vec<GlideEvent> },
}
impl Default for LaneKind {
fn default() -> Self {
LaneKind::Points(Vec::new())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LfoState {
pub armed: bool,
pub speed: Q8_8,
pub depth: Q15,
pub waveform: Waveform,
pub retrig: bool,
}
impl Default for LfoState {
fn default() -> Self {
Self {
armed: false,
speed: Q8_8::ZERO,
depth: Q15::ZERO,
waveform: Waveform::BipolarSine,
retrig: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SlideState {
pub armed: bool,
pub rate: Q15,
pub fine: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GlideState {
pub armed: bool,
pub target: Pitch,
pub rate: i16,
}
impl Default for GlideState {
fn default() -> Self {
Self {
armed: false,
target: Pitch::C5,
rate: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LaneEvent<'a> {
Point(&'a AutomationPoint),
Lfo(&'a LfoEvent),
Slide(&'a SlideEvent),
Glide(&'a GlideEvent),
}
impl<'a> LaneEvent<'a> {
#[inline]
pub fn tick(&self) -> u32 {
match self {
Self::Point(p) => p.tick,
Self::Lfo(e) => e.tick(),
Self::Slide(e) => e.tick(),
Self::Glide(e) => e.tick(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AutomationLane {
pub target: AutomationTarget,
pub kind: LaneKind,
pub enabled: bool,
#[serde(default)]
pub scope: ModScope,
#[serde(default)]
pub song: u16,
}
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ModScope {
#[default]
Track,
Voice,
}
impl AutomationLane {
pub fn new(target: AutomationTarget) -> Self {
Self {
target,
kind: LaneKind::default(),
enabled: true,
scope: ModScope::Track,
song: 0,
}
}
pub fn new_with_kind(target: AutomationTarget, kind: LaneKind) -> Self {
Self {
target,
kind,
enabled: true,
scope: ModScope::Track,
song: 0,
}
}
pub fn with_song(mut self, song: u16) -> Self {
self.song = song;
self
}
#[inline]
pub fn points(&self) -> Option<&[AutomationPoint]> {
if let LaneKind::Points(p) = &self.kind {
Some(p)
} else {
None
}
}
#[inline]
pub fn points_mut(&mut self) -> Option<&mut Vec<AutomationPoint>> {
if let LaneKind::Points(p) = &mut self.kind {
Some(p)
} else {
None
}
}
pub fn insert_point(&mut self, point: AutomationPoint) {
let points = self
.points_mut()
.expect("insert_point called on non-Points lane");
match points.binary_search_by_key(&point.tick, |p| p.tick) {
Ok(idx) => points[idx] = point,
Err(idx) => points.insert(idx, point),
}
}
pub fn events_in(&self, start_tick: u32, end_tick: u32) -> alloc::vec::Vec<LaneEvent<'_>> {
let in_window = |t: u32| t >= start_tick && t < end_tick;
let mut out: alloc::vec::Vec<LaneEvent<'_>> = alloc::vec::Vec::new();
match &self.kind {
LaneKind::Points(points) => out.extend(
points
.iter()
.filter(|p| in_window(p.tick))
.map(LaneEvent::Point),
),
LaneKind::Lfo { events } => out.extend(
events
.iter()
.filter(|e| in_window(e.tick()))
.map(LaneEvent::Lfo),
),
LaneKind::Slide { events } => out.extend(
events
.iter()
.filter(|e| in_window(e.tick()))
.map(LaneEvent::Slide),
),
LaneKind::Glide { events } => out.extend(
events
.iter()
.filter(|e| in_window(e.tick()))
.map(LaneEvent::Glide),
),
}
out
}
pub fn value_at(&self, tick: u32) -> Option<AutomationValue> {
let points = self.points()?;
if points.is_empty() {
return None;
}
let idx = match points.binary_search_by_key(&tick, |p| p.tick) {
Ok(i) => i,
Err(0) => return None,
Err(i) => i - 1,
};
Some(points[idx].value)
}
pub fn lfo_state_at(&self, tick: u32) -> Option<LfoState> {
let events = if let LaneKind::Lfo { events } = &self.kind {
events
} else {
return None;
};
let mut s = LfoState::default();
for e in events {
if e.tick() > tick {
break;
}
match *e {
LfoEvent::Set {
speed,
depth,
waveform,
retrig,
..
} => {
s.armed = true;
s.speed = speed;
s.depth = depth;
s.waveform = waveform;
s.retrig = retrig;
}
LfoEvent::DepthOnly { depth, .. } => {
s.armed = true;
s.depth = depth;
}
LfoEvent::SpeedOnly { speed, .. } => {
s.armed = true;
s.speed = speed;
}
LfoEvent::WaveformOnly {
waveform, retrig, ..
} => {
s.waveform = waveform;
s.retrig = retrig;
}
LfoEvent::Clear { .. } => {
s.armed = false;
}
}
}
Some(s)
}
pub fn slide_state_at(&self, tick: u32) -> Option<SlideState> {
let events = if let LaneKind::Slide { events } = &self.kind {
events
} else {
return None;
};
let mut s = SlideState::default();
for e in events {
if e.tick() > tick {
break;
}
match *e {
SlideEvent::Set { rate, fine, .. } => {
s.armed = true;
s.rate = rate;
s.fine = fine;
}
SlideEvent::Clear { .. } => {
s.armed = false;
}
}
}
Some(s)
}
pub fn glide_state_at(&self, tick: u32) -> Option<GlideState> {
let events = if let LaneKind::Glide { events } = &self.kind {
events
} else {
return None;
};
let mut s = GlideState::default();
for e in events {
if e.tick() > tick {
break;
}
match *e {
GlideEvent::Set { target, rate, .. } => {
s.armed = true;
s.target = target;
s.rate = rate;
}
GlideEvent::Clear { .. } => {
s.armed = false;
}
}
}
Some(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn pts_lane() -> AutomationLane {
let mut lane = AutomationLane::new(AutomationTarget::Bpm);
lane.insert_point(AutomationPoint {
tick: 0,
value: AutomationValue::Bpm(125),
});
lane.insert_point(AutomationPoint {
tick: 96,
value: AutomationValue::Bpm(140),
});
lane.insert_point(AutomationPoint {
tick: 192,
value: AutomationValue::Bpm(100),
});
lane
}
#[test]
fn value_at_returns_latest_point() {
let lane = pts_lane();
assert_eq!(lane.value_at(0), Some(AutomationValue::Bpm(125)));
assert_eq!(lane.value_at(50), Some(AutomationValue::Bpm(125)));
assert_eq!(lane.value_at(96), Some(AutomationValue::Bpm(140)));
assert_eq!(lane.value_at(150), Some(AutomationValue::Bpm(140)));
assert_eq!(lane.value_at(192), Some(AutomationValue::Bpm(100)));
assert_eq!(lane.value_at(1000), Some(AutomationValue::Bpm(100)));
}
#[test]
fn value_at_returns_none_before_first_point() {
let mut lane = AutomationLane::new(AutomationTarget::Bpm);
lane.insert_point(AutomationPoint {
tick: 100,
value: AutomationValue::Bpm(120),
});
assert_eq!(lane.value_at(0), None);
assert_eq!(lane.value_at(99), None);
assert_eq!(lane.value_at(100), Some(AutomationValue::Bpm(120)));
}
#[test]
fn value_at_is_none_for_non_points_kind() {
let lane = AutomationLane::new_with_kind(
AutomationTarget::TrackPitch(0),
LaneKind::Lfo { events: vec![] },
);
assert_eq!(lane.value_at(0), None);
}
#[test]
fn events_in_filters_window_for_points() {
let lane = pts_lane();
let evs: Vec<u32> = lane.events_in(50, 200).iter().map(|e| e.tick()).collect();
assert_eq!(evs, vec![96, 192]);
}
#[test]
fn events_in_filters_window_for_lfo() {
let lane = AutomationLane::new_with_kind(
AutomationTarget::TrackPitch(0),
LaneKind::Lfo {
events: vec![
LfoEvent::Set {
tick: 0,
speed: Q8_8::from_int(3),
depth: Q15::from_ratio(1, 4),
waveform: Waveform::BipolarSine,
retrig: true,
},
LfoEvent::Clear { tick: 24 },
],
},
);
let evs: Vec<u32> = lane.events_in(0, 48).iter().map(|e| e.tick()).collect();
assert_eq!(evs, vec![0, 24]);
let evs: Vec<u32> = lane.events_in(0, 24).iter().map(|e| e.tick()).collect();
assert_eq!(evs, vec![0]); }
#[test]
fn new_targets_are_compatible_with_normalized_values() {
let v = AutomationValue::Normalized(Q15::ZERO);
assert!(v.is_compatible_with(AutomationTarget::TrackPitch(7)));
assert!(v.is_compatible_with(AutomationTarget::TrackChannelVolume(3)));
}
#[test]
fn pitch_value_is_track_pitch_only_and_holds() {
use crate::core::fixed::units::PitchDelta;
let v = AutomationValue::Pitch(PitchDelta::from_semitones(12));
assert!(v.is_compatible_with(AutomationTarget::TrackPitch(0)));
assert!(!v.is_compatible_with(AutomationTarget::Bpm));
assert!(!v.is_compatible_with(AutomationTarget::GlobalVolume));
assert!(!v.is_compatible_with(AutomationTarget::TrackVolume(0)));
let mut lane = AutomationLane::new(AutomationTarget::TrackPitch(0));
lane.insert_point(AutomationPoint { tick: 0, value: v });
lane.insert_point(AutomationPoint {
tick: 64,
value: AutomationValue::Pitch(PitchDelta::from_semitones(-5)),
});
assert_eq!(lane.value_at(10), Some(v)); assert_eq!(
lane.value_at(64),
Some(AutomationValue::Pitch(PitchDelta::from_semitones(-5)))
);
assert_eq!(
lane.value_at(1000),
Some(AutomationValue::Pitch(PitchDelta::from_semitones(-5)))
);
}
fn lfo_lane(events: Vec<LfoEvent>) -> AutomationLane {
AutomationLane::new_with_kind(AutomationTarget::TrackPitch(0), LaneKind::Lfo { events })
}
#[test]
fn lfo_state_at_pre_first_event_is_default() {
let lane = lfo_lane(vec![LfoEvent::Set {
tick: 100,
speed: Q8_8::from_int(3),
depth: Q15::from_ratio(1, 4),
waveform: Waveform::BipolarSquare,
retrig: false,
}]);
let s = lane.lfo_state_at(0).expect("lfo lane");
assert!(!s.armed);
assert_eq!(s.speed, Q8_8::ZERO);
assert_eq!(s.depth, Q15::ZERO);
assert_eq!(s.waveform, Waveform::BipolarSine);
assert!(s.retrig);
}
#[test]
fn lfo_state_at_set_arms_and_carries_params() {
let lane = lfo_lane(vec![LfoEvent::Set {
tick: 100,
speed: Q8_8::from_int(3),
depth: Q15::from_ratio(1, 4),
waveform: Waveform::BipolarSquare,
retrig: false,
}]);
let s = lane.lfo_state_at(100).unwrap();
assert!(s.armed);
assert_eq!(s.speed, Q8_8::from_int(3));
assert_eq!(s.depth, Q15::from_ratio(1, 4));
assert_eq!(s.waveform, Waveform::BipolarSquare);
assert!(!s.retrig);
let s2 = lane.lfo_state_at(1000).unwrap();
assert!(s2.armed);
assert_eq!(s2, s);
}
#[test]
fn lfo_state_at_clear_disarms_but_keeps_params() {
let lane = lfo_lane(vec![
LfoEvent::Set {
tick: 0,
speed: Q8_8::from_int(3),
depth: Q15::from_ratio(1, 4),
waveform: Waveform::BipolarSine,
retrig: true,
},
LfoEvent::Clear { tick: 12 },
]);
let s = lane.lfo_state_at(11).unwrap();
assert!(s.armed);
let s = lane.lfo_state_at(12).unwrap();
assert!(!s.armed, "Clear should disarm at its tick");
assert_eq!(s.speed, Q8_8::from_int(3));
assert_eq!(s.depth, Q15::from_ratio(1, 4));
}
#[test]
fn lfo_state_at_depth_only_updates_only_depth() {
let lane = lfo_lane(vec![
LfoEvent::Set {
tick: 0,
speed: Q8_8::from_int(3),
depth: Q15::from_ratio(1, 4),
waveform: Waveform::BipolarSine,
retrig: true,
},
LfoEvent::DepthOnly {
tick: 6,
depth: Q15::from_ratio(1, 2),
},
]);
let s = lane.lfo_state_at(6).unwrap();
assert!(s.armed);
assert_eq!(s.speed, Q8_8::from_int(3));
assert_eq!(s.depth, Q15::from_ratio(1, 2));
}
#[test]
fn lfo_state_at_speed_only_updates_only_speed() {
let lane = lfo_lane(vec![
LfoEvent::Set {
tick: 0,
speed: Q8_8::from_int(3),
depth: Q15::from_ratio(1, 4),
waveform: Waveform::BipolarSine,
retrig: true,
},
LfoEvent::SpeedOnly {
tick: 6,
speed: Q8_8::from_int(7),
},
]);
let s = lane.lfo_state_at(6).unwrap();
assert_eq!(s.speed, Q8_8::from_int(7));
assert_eq!(s.depth, Q15::from_ratio(1, 4));
}
#[test]
fn lfo_state_at_returns_none_for_non_lfo_kind() {
let lane = AutomationLane::new(AutomationTarget::Bpm);
assert!(lane.lfo_state_at(0).is_none());
}
fn slide_lane(events: Vec<SlideEvent>) -> AutomationLane {
AutomationLane::new_with_kind(AutomationTarget::TrackVolume(0), LaneKind::Slide { events })
}
#[test]
fn slide_state_replays_set_and_clear() {
let lane = slide_lane(vec![
SlideEvent::Set {
tick: 0,
rate: Q15::from_raw(5),
fine: false,
},
SlideEvent::Clear { tick: 12 },
]);
let s = lane.slide_state_at(0).unwrap();
assert!(s.armed && s.rate == Q15::from_raw(5) && !s.fine);
let s = lane.slide_state_at(12).unwrap();
assert!(!s.armed);
let s = lane.slide_state_at(0).unwrap();
assert!(s.armed); }
fn glide_lane(events: Vec<GlideEvent>) -> AutomationLane {
AutomationLane::new_with_kind(AutomationTarget::TrackPitch(0), LaneKind::Glide { events })
}
#[test]
fn glide_state_replays_set_retarget_clear() {
let lane = glide_lane(vec![
GlideEvent::Set {
tick: 0,
target: Pitch::C5,
rate: 5,
},
GlideEvent::Set {
tick: 6,
target: Pitch::E5,
rate: 5,
},
GlideEvent::Clear { tick: 12 },
]);
let s = lane.glide_state_at(0).unwrap();
assert!(s.armed && s.target == Pitch::C5);
let s = lane.glide_state_at(6).unwrap();
assert!(s.armed && s.target == Pitch::E5);
let s = lane.glide_state_at(12).unwrap();
assert!(!s.armed);
}
}