use dawkey_core::{AutomationLane, AutomationTarget, Command, PointSpec, TrackId, WriteAutomation};
use crate::App;
use crate::state::{AutomationMode, AutomationPass, AutomationWrite, Gesture};
const MIN_STEP_S: f64 = 0.005;
impl App {
pub(crate) fn control_moved(
&mut self,
track: TrackId,
target: AutomationTarget,
value: f64,
merge_key: &str,
set: Command,
) -> Result<(), String> {
if !self.writing_automation() {
return self.execute_merging(set, merge_key);
}
self.write_automation_point(track, target, value, merge_key)
}
pub(crate) fn writing_automation(&self) -> bool {
self.view.automation_write && self.studio.transport().playing
}
pub(crate) fn value_now(&self, track: TrackId, target: &AutomationTarget, fixed: f64) -> f64 {
let project = self.studio.session().project();
let frame = project.seconds_to_frames(self.studio.transport().position_s);
project
.track(track)
.and_then(|t| t.lane(target))
.and_then(|l: &AutomationLane| l.value_at(frame))
.unwrap_or(fixed)
}
pub(crate) fn cycle_automation_mode(&mut self) {
let next = self.view.automation_mode.next();
self.end_automation_pass();
self.view.automation_mode = next;
}
fn write_key(&mut self, gesture_key: &str) -> String {
if self.view.automation_mode == AutomationMode::Touch {
return gesture_key.to_owned();
}
let passes = &mut self.view.passes;
self.view
.pass
.get_or_insert_with(|| {
*passes += 1;
AutomationPass {
merge_key: format!("automation pass {passes}"),
every_lane: false,
}
})
.merge_key
.clone()
}
fn write_automation_point(
&mut self,
track: TrackId,
target: AutomationTarget,
value: f64,
merge_key: &str,
) -> Result<(), String> {
let (command, key) = self.automation_point(track, target, value, merge_key);
self.execute_merging(command, &key)
}
fn automation_point(
&mut self,
track: TrackId,
target: AutomationTarget,
value: f64,
merge_key: &str,
) -> (Command, String) {
let now = self.studio.transport().position_s;
let key = self.write_key(merge_key);
let found = self
.view
.writing
.iter()
.position(|w| w.track == track && w.target == target);
let index = match found {
Some(i) => {
let write = &mut self.view.writing[i];
if write.points.last().is_some_and(|&(t, _)| t > now) {
write.from_s = now;
write.points.clear();
}
i
}
None => {
self.view.writing.push(AutomationWrite {
track,
target: target.clone(),
from_s: now,
points: Vec::new(),
});
self.view.writing.len() - 1
}
};
let write = &mut self.view.writing[index];
let points = &mut write.points;
match points[..] {
[.., (t, _)] if now - t < MIN_STEP_S => points.last_mut().expect("matched").1 = value,
[.., (_, a), (_, b)] if a == value && b == value => {
points.last_mut().expect("matched").0 = now;
}
_ => points.push((now, value)),
}
let command = WriteAutomation {
track,
target,
from_s: write.from_s,
to_s: now,
points: points
.iter()
.map(|&(time_s, value)| PointSpec { time_s, value })
.collect(),
};
(command.into(), key)
}
pub(crate) fn touch(&mut self, gesture: &Gesture) -> Result<(), String> {
if !self.writing_automation() {
return Ok(());
}
let (track, target, value, key) = match gesture {
Gesture::Fader {
track,
start_db,
merge_key,
} => (
*track,
AutomationTarget::Gain,
f64::from(*start_db),
merge_key,
),
Gesture::Pan {
track,
start_pan,
merge_key,
} => (
*track,
AutomationTarget::Pan,
f64::from(*start_pan),
merge_key,
),
Gesture::PluginParam {
plugin,
param,
start_value,
merge_key,
..
} => {
let project = self.studio.session().project();
let Some((track, _)) = project.plugin(*plugin) else {
return Ok(());
};
let target = AutomationTarget::Param {
plugin: *plugin,
param: param.clone(),
};
(track.id, target, *start_value, merge_key)
}
_ => return Ok(()),
};
self.write_automation_point(track, target, value, &key.clone())
}
pub(crate) fn release_automation(&mut self) {
if self.view.pass.is_some() && self.writing_automation() {
return;
}
self.view.writing.clear();
self.studio.end_merge();
}
pub(crate) fn keep_writing_automation(&mut self) {
if !self.writing_automation() {
self.end_automation_pass();
return;
}
if self.view.automation_mode == AutomationMode::Write
&& !self.view.pass.as_ref().is_some_and(|p| p.every_lane)
{
self.write_every_lane();
}
if self.view.pass.is_none() && self.view.gesture.is_none() {
return;
}
let writes: Vec<(TrackId, AutomationTarget, f64)> = self
.view
.writing
.iter()
.filter_map(|w| Some((w.track, w.target.clone(), w.points.last()?.1)))
.collect();
let key = match &self.view.gesture {
Some(
Gesture::Fader { merge_key, .. }
| Gesture::Pan { merge_key, .. }
| Gesture::PluginParam { merge_key, .. },
) => merge_key.clone(),
_ => String::new(),
};
self.write_all(writes, &key);
}
fn write_all(&mut self, writes: Vec<(TrackId, AutomationTarget, f64)>, gesture_key: &str) {
let mut commands = Vec::with_capacity(writes.len());
let mut key = String::new();
for (track, target, value) in writes {
let (command, k) = self.automation_point(track, target, value, gesture_key);
commands.push(command);
key = k;
}
if commands.is_empty() {
return;
}
if let Err(e) = self.studio.execute_merging_all(commands, &key) {
self.message = Some(e.to_string());
}
}
fn write_every_lane(&mut self) {
let project = self.studio.session().project();
let frame = project.seconds_to_frames(self.studio.transport().position_s);
let lanes: Vec<(TrackId, AutomationTarget, f64)> = project
.all_tracks()
.flat_map(|t| {
t.automation
.iter()
.filter_map(move |l| Some((t.id, l.target.clone(), l.value_at(frame)?)))
})
.collect();
self.write_key("");
if let Some(pass) = &mut self.view.pass {
pass.every_lane = true;
}
let writing = &self.view.writing;
let lanes = lanes
.into_iter()
.filter(|(track, target, _)| {
!writing
.iter()
.any(|w| w.track == *track && w.target == *target)
})
.collect();
self.write_all(lanes, "");
}
pub(crate) fn end_automation_pass(&mut self) {
let Some(pass) = self.view.pass.take() else {
return;
};
self.view.writing.clear();
self.studio.end_merge();
if pass.every_lane || self.view.automation_mode == AutomationMode::Write {
self.view.automation_mode = AutomationMode::Touch;
}
}
}