klavier_jack/
tracker.rs

1use std::{fmt::Display, sync::{Arc, Mutex}, thread, rc::Rc};
2
3use klavier_core::{bar::Bar, rhythm::Rhythm, key::Key, note::Note, project::ModelChangeMetadata, tempo::Tempo, ctrl_chg::CtrlChg, global_repeat::RenderRegionWarning};
4use klavier_helper::{bag_store::BagStore, store::Store};
5use crate::player::PlayError;
6use error_stack::Report;
7use tracing::error;
8use crate::player;
9use klavier_core::play_start_tick::PlayStartTick;
10use klavier_core::repeat::AccumTick;
11
12#[cfg(not(test))]
13use player::JackClientProxy;
14
15#[cfg(test)]
16use player::TestJackClientProxy as JackClientProxy;
17
18//#[cfg(not(test))]
19use player::Player;
20
21#[derive(Debug, PartialEq, Eq, Clone, Copy)]
22pub enum Status {
23  Stopped,
24  StartingPlay { seq: usize },
25  Playing { seq: usize, tick: u32, accum_tick: AccumTick },
26  Disconnected,
27}
28
29pub struct Tracker {
30  seq: usize,
31  player: Option<Player>,
32  status: Arc<Mutex<Status>>,
33}
34
35#[derive(Debug, PartialEq)]
36pub enum TrackerError {
37  PlayerNotOpened,
38  PlayerStopping { seq: usize },
39  PlayerErr(PlayError),
40  NotPlaying(Status),
41}
42
43impl Display for TrackerError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45      write!(f, "{:?}", self)
46    }
47}
48
49impl std::error::Error for TrackerError {}
50
51impl Tracker {
52  pub fn run(
53    name: &str, mut client_factory: Option<Box<dyn FnOnce(&str, jack::ClientOptions) -> core::result::Result<JackClientProxy, jack::Error>>>
54  ) -> core::result::Result<(Self, jack::ClientStatus), Report<jack::Error>> {
55      let (mut player, client_status) = Player::open(name, client_factory.take())?;
56      let receiver = player.take_resp().unwrap();
57      let status = Arc::new(Mutex::new(Status::Stopped));
58      let moved_status = status.clone();
59      thread::spawn(move || {
60        loop {
61          match receiver.recv() {
62            Ok(resp) => match resp {
63              player::Resp::Err { seq, error } => {
64                tracing::error!("Player command error seq: {:?}, error: {:?}", seq, error);
65              }
66              player::Resp::Info { seq: _, info } => match info {
67                player::CmdInfo::PlayingEnded => {
68                  *moved_status.lock().unwrap() = Status::Stopped;
69                }
70                player::CmdInfo::CurrentLoc{ seq, tick, accum_tick } => {
71                  *moved_status.lock().unwrap() = Status::Playing { seq, tick, accum_tick }
72                }
73              }
74              player::Resp::Ok { seq } => {
75                tracing::info!("Player command ok seq: {}", seq);
76              }
77            }
78            Err(err) => {
79              println!("recv error: {:?}", err);
80              error!("Player receiver disconnected {:?}", err);
81              *moved_status.lock().unwrap() = Status::Disconnected;
82              break;
83            }
84          }
85        }
86      });      
87
88      Ok((
89        Self {
90          seq: 0,
91          player: Some(player),
92          status,
93        },
94        client_status
95      ))
96  }
97
98  pub fn play(
99    &mut self,
100    seq: usize, play_start_tick: Option<PlayStartTick>, top_rhythm: Rhythm, top_key: Key,
101    note_repo: &BagStore<u32, Rc<Note>, ModelChangeMetadata>,
102    bar_repo: &Store<u32, Bar, ModelChangeMetadata>,
103    tempo_repo: &Store<u32, Tempo, ModelChangeMetadata>,
104    dumper_repo: &Store<u32, CtrlChg, ModelChangeMetadata>,
105    soft_repo: &Store<u32, CtrlChg, ModelChangeMetadata>,
106  ) -> core::result::Result<Vec<RenderRegionWarning>, Report<TrackerError>> {
107    match &mut self.player {
108      Some(jack) => {
109        let p = jack.play(
110          self.seq, play_start_tick,
111          top_rhythm, top_key, note_repo, bar_repo, tempo_repo, dumper_repo, soft_repo
112        ).map_err(|e| {
113          let top = TrackerError::PlayerErr(e.current_context().clone());
114          e.change_context(top)
115        })?;
116        *self.status.lock().unwrap() = Status::StartingPlay { seq };
117        self.seq += 1;
118
119        Ok(p)
120      }
121      None => Err(Report::new(TrackerError::PlayerNotOpened))?,
122    }
123  }
124
125  pub fn stop(&mut self, _seq: usize) -> core::result::Result<(), Report<TrackerError>> {
126    match &mut self.player {
127      Some(jack)=> {
128        let mut pstatus = self.status.lock().unwrap();
129        if let Status::Playing { seq: _, tick: _, accum_tick: _ } = *pstatus {
130          jack.stop(self.seq).map_err(|e| {
131            let top = TrackerError::PlayerErr(e.current_context().clone());
132            e.change_context(top)
133          })?;
134          *pstatus = Status::Stopped;
135        } else {
136          Err(Report::new(TrackerError::NotPlaying(*pstatus)))?
137        }
138
139        Ok(())
140      }
141      None => Err(Report::new(TrackerError::PlayerNotOpened))?,
142    }
143  }
144
145  pub fn status(&self) -> Status {
146    *self.status.lock().unwrap()
147  }
148}
149
150#[cfg(test)]
151mod tests {
152  use jack::ClientStatus;
153  use klavier_core::{rhythm::Rhythm, key::Key};
154  use klavier_helper::{bag_store::BagStore, store::Store};
155  use crate::tracker::{Status, TrackerError};
156  use super::Tracker;
157  use crate::player::TestJackClientProxy;
158
159  #[test]
160  fn new() {
161    let factory = Box::new(|app_name: &str, _options|
162      Ok(
163        TestJackClientProxy {
164          status: ClientStatus::empty(),
165          app_name: app_name.to_owned(),
166          buffer_size: 2048,
167          sampling_rate: 48000,
168        }
169      )
170    );
171
172    let (tracker, _status) = Tracker::run("app name", Some(factory)).unwrap();
173    assert_eq!(tracker.seq, 0);
174    assert_eq!(tracker.player.is_none(), false);
175    assert_eq!(tracker.status(), Status::Stopped);
176  }
177
178  #[test]
179  fn disconnected_stop() {
180    let factory = Box::new(|app_name: &str, _options|
181      Ok(
182        TestJackClientProxy {
183          status: ClientStatus::empty(),
184          app_name: app_name.to_owned(),
185          buffer_size: 2048,
186          sampling_rate: 48000,
187        }
188      )
189    );
190    let (mut tracker, _status) = Tracker::run("app name", Some(factory)).unwrap();
191    assert_eq!(tracker.status(), Status::Stopped);
192    let result = tracker.stop(1).err().unwrap();
193    assert_eq!(*result.current_context(), TrackerError::NotPlaying(Status::Stopped));
194  }
195
196  #[test]
197  fn run() {
198    let factory = Box::new(|app_name: &str, _options|
199      Ok(
200        TestJackClientProxy {
201          status: ClientStatus::INIT_FAILURE,
202          app_name: app_name.to_owned(),
203          buffer_size: 2048,
204          sampling_rate: 48000,
205        }
206      )
207    );
208    let (mut tracker, status) = Tracker::run("my name", Some(factory)).unwrap();
209    assert_eq!(status, jack::ClientStatus::INIT_FAILURE);
210    let play_start_loc = None;
211
212    let result = tracker.play(
213      1, play_start_loc, Rhythm::new(2, 4),
214      Key::SHARP_1,
215      &BagStore::new(false),
216      &Store::new(false),
217      &Store::new(false),
218      &Store::new(false),
219      &Store::new(false),
220    );
221    assert_eq!(result.is_ok(), true);
222    assert_eq!(tracker.status(), Status::StartingPlay { seq: 1 });
223  }
224}