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
18use 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}