use crate::runtime::persistent_state::PersistentState;
use rusty_link::{AblLink, SessionState};
use std::{
sync::{
Arc,
atomic::{AtomicU64, Ordering::Relaxed},
},
time::{Duration, Instant},
};
pub(crate) static CONNECTED_PEERS: AtomicU64 = AtomicU64::new(0);
const BEAT_PROGRESSION_OFFSET: f32 = 40.0;
const LINK_QUANTUM: f64 = 4.0;
pub(crate) use gled_model::timing::BeatState;
pub(crate) fn sample_beat_state(link: &AblLink, framerate: f32) -> BeatState {
let mut session_state = SessionState::default();
link.capture_audio_session_state(&mut session_state);
let now = link.clock_micros();
BeatState {
beat_progression: BEAT_PROGRESSION_OFFSET
+ session_state.beat_at_time(now, LINK_QUANTUM) as f32,
beats_per_minute: session_state.tempo() as f32,
framerate,
}
}
pub(crate) struct Timing {
link: Arc<AblLink>,
beats_per_minute: f32,
previous_change_beats_per_minute: f32,
pub(crate) change_beats_per_minute: f32,
beat_progression: f32,
framerate: Option<f32>,
taps: [Option<Instant>; 4],
tap_count: usize,
}
impl std::fmt::Debug for Timing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Timing")
.field("beats_per_minute", &self.beats_per_minute)
.field("change_beats_per_minute", &self.change_beats_per_minute)
.finish_non_exhaustive()
}
}
impl Default for Timing {
fn default() -> Self {
let link = AblLink::new(120.0);
link.enable(true);
Self {
link: Arc::new(link),
beats_per_minute: 120.0,
previous_change_beats_per_minute: 120.0,
change_beats_per_minute: 120.0,
beat_progression: 0.0,
framerate: None,
taps: [None; 4],
tap_count: 0,
}
}
}
impl Timing {
pub(crate) fn beat_progression(&self) -> f32 {
self.beat_progression
}
pub(crate) fn beats_per_minute(&self) -> f32 {
self.beats_per_minute
}
pub(crate) fn set_framerate(&mut self, framerate: f32) {
self.framerate = Some(framerate);
}
pub(crate) fn beat_state(&self) -> BeatState {
BeatState {
beat_progression: self.beat_progression,
beats_per_minute: self.beats_per_minute,
framerate: self.framerate.unwrap_or_default(),
}
}
pub(crate) fn link(&self) -> Arc<AblLink> {
Arc::clone(&self.link)
}
#[cfg_attr(feature = "profiling", profiling::function)]
pub(crate) fn tick(&mut self, persistent_state: &PersistentState) {
self.set_link_values(persistent_state);
self.get_link_values();
self.remove_old_taps();
}
#[cfg_attr(feature = "profiling", profiling::function)]
fn get_link_values(&mut self) {
CONNECTED_PEERS.store(self.link.num_peers(), Relaxed);
let mut session_state = SessionState::default();
self.link.capture_app_session_state(&mut session_state);
let now = self.link.clock_micros();
self.beat_progression =
BEAT_PROGRESSION_OFFSET + session_state.beat_at_time(now, LINK_QUANTUM) as f32;
self.beats_per_minute = session_state.tempo() as f32;
}
#[cfg_attr(feature = "profiling", profiling::function)]
fn set_link_values(&mut self, persistent_state: &PersistentState) {
if persistent_state.ableton_link_read_only() {
self.previous_change_beats_per_minute = self.beats_per_minute;
self.change_beats_per_minute = self.beats_per_minute;
return;
}
if self.previous_change_beats_per_minute == self.change_beats_per_minute {
self.previous_change_beats_per_minute = self.beats_per_minute;
self.change_beats_per_minute = self.beats_per_minute;
return;
}
let mut session_state = SessionState::default();
self.link.capture_app_session_state(&mut session_state);
let now = self.link.clock_micros();
session_state.set_tempo(self.change_beats_per_minute as f64, now);
self.link.commit_app_session_state(&session_state);
self.previous_change_beats_per_minute = self.change_beats_per_minute;
}
fn remove_old_taps(&mut self) {
let mut reset_tap_count = true;
for tap in self.taps.iter_mut() {
if let Some(date) = tap {
if date.elapsed() > Duration::from_secs(10) {
tap.take();
} else {
reset_tap_count = false;
}
}
}
if reset_tap_count {
self.tap_count = 0;
}
}
pub(crate) fn multiply_speed(&mut self, multiplier: f32, persistent_state: &PersistentState) {
if persistent_state.ableton_link_read_only() {
return;
}
self.change_beats_per_minute *= multiplier;
}
pub(crate) fn add_speed(&mut self, delta: f32, persistent_state: &PersistentState) {
if persistent_state.ableton_link_read_only() {
return;
}
self.change_beats_per_minute += delta;
}
pub(crate) fn tap_count(&self) -> usize {
self.tap_count
}
pub(crate) fn set_beats_per_minute(&mut self, beats_per_minute: f32) {
self.change_beats_per_minute = beats_per_minute;
}
pub(crate) fn tap(&mut self, persistent_state: &PersistentState) {
if persistent_state.ableton_link_read_only() {
return;
}
let link_now = self.link.clock_micros();
self.tap_count += 1;
let now = Instant::now();
self.taps[0] = Some(now);
self.taps.sort();
let first = self
.taps
.iter()
.filter_map(|tap| *tap)
.next()
.expect("There must be at least one entry");
if first == now {
return;
}
let beat_time_first = now.duration_since(first);
let avg_beat_time = (beat_time_first.as_nanos())
/ (self.taps.iter().filter(|tap| tap.is_some()).count() as u128 - 1);
let new_beats_per_minute = (60e+9f64 / f64::from(avg_beat_time as u32)) as f32;
let offset = self.beat_progression % 4.0;
let goal_offset = (self.tap_count - 1) as f32 % 4.0;
let mut session_state = SessionState::default();
self.link.capture_app_session_state(&mut session_state);
session_state.set_tempo(new_beats_per_minute as f64, link_now);
session_state.request_beat_at_time(
(self.beat_progression + goal_offset - offset) as f64,
link_now,
4.0,
);
self.link.commit_app_session_state(&session_state);
}
}