1use std::{
2 fmt::Display,
3 marker::PhantomData,
4 rc::Rc,
5 sync::mpsc::{sync_channel, Receiver, RecvError, SyncSender},
6 thread::{self},
7 time,
8};
9
10use error_stack::Report;
11use jack::{Client, ClientStatus, Control, MidiOut, ProcessScope, RawMidi};
12use klavier_core::{midi_events::{MidiEvents, PlayData, create_midi_events}, play_start_tick::ToAccumTickError, tempo::TempoValue};
13use klavier_core::play_start_tick::PlayStartTick;
14use klavier_core::{
15 bar::Bar,
16 ctrl_chg::CtrlChg,
17 duration::Duration,
18 global_repeat::RenderRegionWarning,
19 key::Key,
20 note::Note,
21 project::ModelChangeMetadata,
22 repeat::{RenderRegionError, AccumTick},
23 rhythm::Rhythm,
24 tempo::Tempo,
25};
26use klavier_helper::{bag_store::BagStore, store::{Store}};
27
28#[cfg(not(test))]
29use jack::MidiWriter;
30
31pub struct Player {
32 pub sampling_rate: u32,
34 cmd_channel: SyncSender<Cmd>,
35 resp_channel: Option<Receiver<Resp>>,
36 closer: Option<Box<dyn Send + 'static + FnOnce() -> Option<jack::Error>>>,
37}
38
39#[derive(Clone)]
40pub enum Cmd {
41 Play {
42 seq: usize,
43 play_data: PlayData,
44 start_cycle: u64,
45 },
46 Stop {
47 seq: usize,
48 },
49}
50
51#[derive(Debug, PartialEq, Eq, Clone)]
52pub enum CmdError {
53 AlreadyPlaying,
54 MidiWriteError(String),
55}
56
57#[derive(Debug, PartialEq, Eq, Clone)]
58pub enum CmdInfo {
59 PlayingEnded,
60 CurrentLoc {
61 seq: usize,
62 tick: u32,
63 accum_tick: AccumTick,
64 },
65}
66
67#[derive(Debug, PartialEq, Eq, Clone)]
68pub enum Resp {
69 Err { seq: Option<usize>, error: CmdError },
70 Info { seq: Option<usize>, info: CmdInfo },
71 Ok { seq: usize },
72}
73
74pub enum PlayState {
75 Init,
76 Playing {
77 seq: usize,
78 current_loc: u64,
79 play_data: PlayData,
80 },
81 Stopping,
82}
83
84#[cfg(not(test))]
85pub struct JackClientProxy {
86 client: Option<Client>,
87 status: ClientStatus,
88}
89
90pub struct TestMidiWriter<'a> {
91 _phantom: PhantomData<&'a ()>,
92}
93
94impl<'a> TestMidiWriter<'a> {
95 pub fn write(&mut self, _message: &RawMidi) -> core::result::Result<(), jack::Error> {
96 Ok(())
97 }
98}
99
100#[cfg(not(test))]
101impl JackClientProxy {
102 pub fn new(app_name: &str, options: jack::ClientOptions) -> core::result::Result<Self, jack::Error> {
103 let (client, status) = jack::Client::new(app_name, options)?;
104 Ok(Self {
105 client: Some(client),
106 status,
107 })
108 }
109
110 pub fn buffer_size(&self) -> u32 {
111 self.client.as_ref().map(|c| c.buffer_size()).unwrap()
112 }
113
114 pub fn sampling_rate(&self) -> u32 {
115 self.client.as_ref().map(|c| c.sample_rate()).unwrap() as u32
116 }
117
118 pub fn midi_out_port(&self) -> core::result::Result<Port<MidiOut>, jack::Error> {
119 Ok(self
120 .client
121 .as_ref()
122 .map(|c| c.register_port("MIDI OUT", jack::MidiOut::default()))
123 .unwrap()?)
124 }
125
126 pub fn closer<F>(
127 &mut self,
128 callback: F,
129 ) -> core::result::Result<Option<Box<dyn Send + 'static + FnOnce() -> Option<jack::Error>>>, jack::Error>
130 where
131 F: 'static + Send + FnMut(&Client, &ProcessScope) -> Control,
132 {
133 let active_client = {
134 let client = self.client.take().unwrap();
135 client.activate_async((), jack::contrib::ClosureProcessHandler::new(callback))?
136 };
137 let closer = move || active_client.deactivate().err();
138 Ok(Some(Box::new(closer)))
139 }
140}
141
142pub struct TestPort<T> {
143 _phantom: PhantomData<T>,
144}
145
146impl<T> TestPort<T> {
147 pub fn writer<'a>(&'a mut self, _ps: &'a ProcessScope) -> TestMidiWriter<'a> {
148 TestMidiWriter {
149 _phantom: PhantomData,
150 }
151 }
152}
153
154pub struct TestJackClientProxy {
155 pub status: ClientStatus,
156
157 pub app_name: String,
158 pub buffer_size: u32,
159 pub sampling_rate: u32,
160}
161
162#[cfg(not(test))]
163use jack::Port;
164
165impl TestJackClientProxy {
166 pub fn new(_app_name: &str, _options: jack::ClientOptions) -> core::result::Result<Self, jack::Error> {
167 panic!("You need to pass client_factory to Player::open() for testing.");
168 }
169
170 pub fn new_test(
171 app_name: &str,
172 _options: jack::ClientOptions,
173 status: ClientStatus,
174 buffer_size: u32,
175 sampling_rate: u32,
176 ) -> core::result::Result<Self, jack::Error> {
177 Ok(Self {
178 status,
179 app_name: app_name.to_owned(),
180 buffer_size,
181 sampling_rate,
182 })
183 }
184
185 pub fn buffer_size(&self) -> u32 {
186 self.buffer_size
187 }
188
189 pub fn sampling_rate(&self) -> u32 {
190 self.sampling_rate
191 }
192
193 pub fn midi_out_port(&self) -> core::result::Result<TestPort<MidiOut>, jack::Error> {
194 Ok(TestPort {
195 _phantom: PhantomData,
196 })
197 }
198
199 pub fn closer<F>(
200 &mut self,
201 _callback: F,
202 ) -> core::result::Result<Option<Box<dyn Send + 'static + FnOnce() -> Option<jack::Error>>>, jack::Error>
203 where
204 F: 'static + Send + FnMut(&Client, &ProcessScope) -> Control,
205 {
206 thread::spawn(move || {
207 let _cb = _callback;
208 thread::sleep(time::Duration::from_secs(60));
210 });
211
212 Ok(None)
213 }
214}
215
216#[cfg(test)]
217use TestJackClientProxy as JCProxy;
218
219#[cfg(not(test))]
220use JackClientProxy as JCProxy;
221
222#[cfg(test)]
223use TestMidiWriter as MW;
224
225#[cfg(not(test))]
226use MidiWriter as MW;
227
228impl Player {
229 fn resp(resp_sender: &SyncSender<Resp>, resp: Resp) {
230 if let Err(e) = resp_sender.send(resp) {
231 println!("Cannot send response: {:?}.", e);
232 }
233 }
234
235 fn perform_cmd(
236 cmd_receiver: &Receiver<Cmd>,
237 pstate: &mut PlayState,
238 resp_sender: &SyncSender<Resp>,
239 ) -> jack::Control {
240 match cmd_receiver.try_recv() {
241 Ok(cmd) => match cmd {
242 Cmd::Play {
243 seq,
244 play_data,
245 start_cycle: start_loc,
246 } => match pstate {
247 PlayState::Init => {
248 *pstate = PlayState::Playing {
249 seq,
250 current_loc: start_loc,
251 play_data,
252 };
253 Self::resp(&resp_sender, Resp::Ok { seq });
254 }
255 PlayState::Playing {
256 seq: _seq,
257 current_loc: _,
258 play_data: _,
259 } => {
260 Self::resp(
261 &resp_sender,
262 Resp::Err {
263 seq: Some(seq),
264 error: CmdError::AlreadyPlaying,
265 },
266 );
267 },
268 PlayState::Stopping => {
269 Self::resp(
270 &resp_sender,
271 Resp::Err {
272 seq: Some(seq),
273 error: CmdError::AlreadyPlaying,
274 },
275 );
276 },
277 },
278 Cmd::Stop { seq } => match pstate {
279 PlayState::Init => {
280 Self::resp(&resp_sender, Resp::Ok { seq });
281 }
282 PlayState::Playing {
283 seq: _seq,
284 current_loc: _,
285 play_data: _,
286 } => {
287 *pstate = PlayState::Stopping;
288 Self::resp(
289 &resp_sender,
290 Resp::Info {
291 seq: Some(seq),
292 info: CmdInfo::PlayingEnded,
293 },
294 );
295 },
296 PlayState::Stopping => {
297 Self::resp(&resp_sender, Resp::Ok { seq });
298 },
299 },
300 },
301 Err(err) => {
302 if err == std::sync::mpsc::TryRecvError::Disconnected {
303 return jack::Control::Quit;
304 }
305 }
306 }
307
308 jack::Control::Continue
309 }
310
311 fn perform_state<'a>(
312 pstate: &mut PlayState,
313 midi_writer: &mut MW<'a>,
314 resp_sender: &SyncSender<Resp>,
315 buffer_size: u32,
316 sampling_rate: u32,
317 ) {
318 match pstate {
319 PlayState::Init => {}
320 PlayState::Playing {
321 seq: playing_seq,
322 current_loc,
323 play_data,
324 } => {
325 let (_idx, data) = play_data
326 .midi_data
327 .range(*current_loc..(*current_loc + buffer_size as u64));
328 for (cycle, midi) in data.iter() {
329 let offset = (cycle - *current_loc) as u32;
330
331 for bytes in midi.iter() {
332 if let Err(e) = midi_writer.write(&jack::RawMidi {
333 time: offset,
334 bytes,
335 }) {
336 Self::resp(
337 &resp_sender,
338 Resp::Err {
339 seq: None,
340 error: CmdError::MidiWriteError(e.to_string()),
341 },
342 )
343 }
344 }
345 }
346
347 let new_loc = *current_loc + buffer_size as u64;
348 let ended = play_data
349 .midi_data
350 .peek_last()
351 .map(|(cycle, _midi)| *cycle < new_loc)
352 .unwrap_or(false);
353 if ended {
354 Self::resp(
355 &resp_sender,
356 Resp::Info {
357 seq: Some(*playing_seq),
358 info: CmdInfo::PlayingEnded,
359 },
360 );
361 *pstate = PlayState::Init;
362 } else {
363 let accum_tick = play_data.cycle_to_tick(*current_loc, sampling_rate);
364 let tick = play_data.accum_tick_to_tick(accum_tick);
365 Self::resp(
366 &resp_sender,
367 Resp::Info {
368 seq: Some(*playing_seq),
369 info: CmdInfo::CurrentLoc {
370 seq: *playing_seq,
371 tick,
372 accum_tick,
373 },
374 },
375 );
376 *current_loc = new_loc;
377 }
378 },
379 PlayState::Stopping => {
380 let mut bytes: Vec<u8> = Vec::with_capacity(3 * 16);
381 for ch in 0..16 {
382 bytes.push(0xB0 + ch); bytes.push(67);
384 bytes.push(0);
385 }
386 if let Err(e) = midi_writer.write(&jack::RawMidi { time: 0, bytes: &bytes }) {
387 Self::resp(
388 &resp_sender,
389 Resp::Err {
390 seq: None,
391 error: CmdError::MidiWriteError(e.to_string()),
392 },
393 )
394 }
395
396 let mut bytes: Vec<u8> = Vec::with_capacity(3 * 16);
397 for ch in 0..16 {
398 bytes.push(0xB0 + ch); bytes.push(120);
400 bytes.push(0);
401 }
402 if let Err(e) = midi_writer.write(&jack::RawMidi { time: 0, bytes: &bytes }) {
403 Self::resp(
404 &resp_sender,
405 Resp::Err {
406 seq: None,
407 error: CmdError::MidiWriteError(e.to_string()),
408 },
409 )
410 }
411
412 let mut bytes: Vec<u8> = Vec::with_capacity(3 * 16);
413 for ch in 0..16 {
414 bytes.push(0xB0 + ch); bytes.push(64);
416 bytes.push(0);
417 }
418 if let Err(e) = midi_writer.write(&jack::RawMidi { time: 0, bytes: &bytes }) {
419 Self::resp(
420 &resp_sender,
421 Resp::Err {
422 seq: None,
423 error: CmdError::MidiWriteError(e.to_string()),
424 },
425 )
426 }
427
428 *pstate = PlayState::Init;
429 },
430 }
431 }
432
433 pub fn open(
434 app_name: &str,
435 client_factory: Option<
436 Box<dyn FnOnce(&str, jack::ClientOptions) -> core::result::Result<JCProxy, jack::Error>>,
437 >,
438 ) -> core::result::Result<(Self, jack::ClientStatus), Report<jack::Error>> {
439 let mut proxy = match client_factory {
440 Some(factory) => factory(app_name, jack::ClientOptions::empty())?,
441 None => JCProxy::new(app_name, jack::ClientOptions::empty())?,
442 };
443 let (cmd_sender, cmd_receiver) = sync_channel::<Cmd>(64);
444 let (resp_sender, resp_receiver) = sync_channel::<Resp>(64);
445
446 let buffer_size: u32 = proxy.buffer_size();
447 let sampling_rate: u32 = proxy.sampling_rate();
448 let mut state = PlayState::Init;
449 let proxy_status = proxy.status;
450
451 let mut midi_out_port = proxy.midi_out_port()?;
452 let callback = move |_client: &jack::Client, ps: &jack::ProcessScope| -> jack::Control {
453 let mut pstate = &mut state;
454 let mut midi_writer = midi_out_port.writer(ps);
455
456 if Self::perform_cmd(&cmd_receiver, &mut pstate, &resp_sender) == jack::Control::Quit {
457 return jack::Control::Quit;
458 }
459
460 Self::perform_state(
461 &mut pstate,
462 &mut midi_writer,
463 &resp_sender,
464 buffer_size,
465 sampling_rate,
466 );
467
468 jack::Control::Continue
469 };
470
471 let closer: Option<Box<dyn Send + 'static + FnOnce() -> Option<jack::Error>>> =
472 proxy.closer(callback)?;
473
474 Ok((
475 Player {
476 sampling_rate,
477 cmd_channel: cmd_sender,
478 resp_channel: Some(resp_receiver),
479 closer,
480 },
481 proxy_status,
482 ))
483 }
484
485 pub fn play(
486 &mut self,
487 seq: usize,
488 play_start_loc: Option<PlayStartTick>,
489 top_rhythm: Rhythm,
490 top_key: Key,
491 note_repo: &BagStore<u32, Rc<Note>, ModelChangeMetadata>,
492 bar_repo: &Store<u32, Bar, ModelChangeMetadata>,
493 tempo_repo: &Store<u32, Tempo, ModelChangeMetadata>,
494 dumper_repo: &Store<u32, CtrlChg, ModelChangeMetadata>,
495 soft_repo: &Store<u32, CtrlChg, ModelChangeMetadata>,
496 ) -> core::result::Result<Vec<RenderRegionWarning>, Report<PlayError>> {
497 let (events, warnings): (MidiEvents, Vec<RenderRegionWarning>) = create_midi_events(
498 top_rhythm,
499 top_key,
500 note_repo,
501 bar_repo,
502 tempo_repo,
503 dumper_repo,
504 soft_repo,
505 )
506 .map_err(|e| PlayError::RenderError(e.current_context().clone()))?;
507
508 let cycles_by_tick: Store<u32, (TempoValue, u64), ()>
509 = events.cycles_by_accum_tick(self.sampling_rate, Duration::TICK_RESOLUTION as u32);
510 let start_accum_tick: u32 = match play_start_loc {
511 None => 0,
512 Some(loc) => {
513 match loc.to_accum_tick(&events.chunks) {
514 Ok(tick) => tick,
515 Err(err) => Err(PlayError::PlayStartTickError(err))?,
516 }
517 }
518 };
519 let start_cycle: u64 = MidiEvents::accum_tick_to_cycle(
520 &cycles_by_tick, start_accum_tick, self.sampling_rate, Duration::TICK_RESOLUTION as u32
521 );
522 let play_data: PlayData = events.to_play_data(cycles_by_tick, self.sampling_rate, Duration::TICK_RESOLUTION as u32);
523
524 self.cmd_channel
525 .send(Cmd::Play { seq, play_data, start_cycle })
526 .map_err(|_e| PlayError::SendError { seq })?;
527 Ok(warnings)
528 }
529
530 pub fn stop(&mut self, seq: usize) -> core::result::Result<(), Report<PlayError>> {
531 self.cmd_channel
532 .send(Cmd::Stop { seq })
533 .map_err(|_e| PlayError::SendError { seq })?;
534 Ok(())
535 }
536
537 pub fn get_resp(&self) -> core::result::Result<Resp, RecvError> {
539 match &self.resp_channel {
540 Some(resp) => Ok(resp.recv()?),
541 None => {
542 panic!("Once you call the take_resp(), you need to receive responses by yourself!")
543 }
544 }
545 }
546
547 pub fn take_resp(&mut self) -> Option<Receiver<Resp>> {
550 self.resp_channel.take()
551 }
552}
553
554impl Drop for Player {
555 fn drop(&mut self) {
556 if let Some(f) = self.closer.take() {
557 let handle = thread::spawn(|| f());
558 for _ in 0..50 {
559 if handle.is_finished() {
560 break;
561 }
562 thread::sleep(time::Duration::from_millis(100));
563 }
564 }
565 }
566}
567
568#[derive(Debug, Clone, PartialEq)]
569pub enum PlayError {
570 RenderError(RenderRegionError),
571 SendError { seq: usize },
572 PlayStartTickError(ToAccumTickError),
573}
574
575impl Display for PlayError {
576 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
577 write!(f, "{:?}", self)
578 }
579}
580
581impl std::error::Error for PlayError {}
582
583#[cfg(test)]
652mod tests {
653 use super::{Cmd, PlayState, Player, Resp};
654 use crate::player::TestJackClientProxy;
655 use crate::player::{CmdError, CmdInfo};
656 use jack::ClientStatus;
657 use klavier_core::duration::Duration;
658 use klavier_core::midi_events::create_midi_events;
659 use klavier_core::{
660 key::Key,
661 note::Note,
662 octave::Octave,
663 pitch::Pitch,
664 project::ModelChangeMetadata,
665 rhythm::Rhythm,
666 sharp_flat::SharpFlat,
667 solfa::Solfa,
668 };
669 use klavier_helper::{bag_store::BagStore, store::Store};
670 use std::{rc::Rc, sync::mpsc::sync_channel};
671
672 #[test]
673 fn play_send_cmd() {
674 let mut note_repo = BagStore::new(false);
675 let note0 = Rc::new(Note {
676 base_start_tick: 100,
677 pitch: Pitch::new(Solfa::F, Octave::Oct4, SharpFlat::Null),
678 ..Default::default()
679 });
680 note_repo.add(
681 note0.start_tick(),
682 note0.clone(),
683 ModelChangeMetadata::default()
684 );
685
686 let (events, _warnings) = create_midi_events(
687 Rhythm::new(2, 4),
688 Key::SHARP_1,
689 ¬e_repo,
690 &Store::new(false),
691 &Store::new(false),
692 &Store::new(false),
693 &Store::new(false),
694 )
695 .unwrap();
696
697 let cycles_by_tick = events.cycles_by_accum_tick(48000, Duration::TICK_RESOLUTION as u32);
698 let play_data0 = events.to_play_data(cycles_by_tick, 48000, 240);
699
700 let factory = Box::new(|app_name: &str, _options| {
701 Ok(TestJackClientProxy {
702 status: ClientStatus::empty(),
703 app_name: app_name.to_owned(),
704 buffer_size: 2048,
705 sampling_rate: 48000,
706 })
707 });
708 let (mut player, _status) = Player::open("my player", Some(factory)).unwrap();
709 let (cmd_sender, cmd_receiver) = sync_channel::<Cmd>(64);
710
711 player.cmd_channel = cmd_sender;
712
713 let _warnings = player
714 .play(
715 1,
716 None,
717 Rhythm::new(2, 4),
718 Key::SHARP_1,
719 ¬e_repo,
720 &Store::new(false),
721 &Store::new(false),
722 &Store::new(false),
723 &Store::new(false),
724 )
725 .unwrap();
726
727 let cmd = cmd_receiver.recv().unwrap();
728
729 match cmd {
730 Cmd::Play { seq, play_data, start_cycle: _ } => {
731 assert_eq!(seq, 1);
732
733 let midi_data0: Vec<&(u64, Vec<Vec<u8>>)> = play_data0.midi_data.iter().collect();
734 let midi_data1: Vec<&(u64, Vec<Vec<u8>>)> = play_data.midi_data.iter().collect();
735 assert_eq!(midi_data0, midi_data1);
736 }
737 Cmd::Stop { seq: _ } => panic!("test failed"),
738 };
739 }
740
741 #[test]
742 fn play_cmd_change_state() {
743 let mut note_repo = BagStore::new(false);
744 let note0 = Rc::new(Note {
745 base_start_tick: 100,
746 pitch: Pitch::new(Solfa::F, Octave::Oct4, SharpFlat::Null),
747 ..Default::default()
748 });
749 note_repo.add(
750 note0.start_tick(),
751 note0.clone(),
752 ModelChangeMetadata::default(),
753 );
754
755 let (events, _warnings) = create_midi_events(
756 Rhythm::new(2, 4),
757 Key::SHARP_1,
758 ¬e_repo,
759 &Store::new(false),
760 &Store::new(false),
761 &Store::new(false),
762 &Store::new(false),
763 )
764 .unwrap();
765
766 let cycles_by_tick = events.cycles_by_accum_tick(48000, Duration::TICK_RESOLUTION as u32);
767 let play_data0 = events.to_play_data(cycles_by_tick, 48000, 240);
768 let factory = Box::new(|app_name: &str, _options| {
769 Ok(TestJackClientProxy {
770 status: ClientStatus::empty(),
771 app_name: app_name.to_owned(),
772 buffer_size: 2048,
773 sampling_rate: 48000,
774 })
775 });
776 let (mut player, _status) = Player::open("my player", Some(factory)).unwrap();
777 let (cmd_sender, cmd_receiver) = sync_channel::<Cmd>(64);
778 let (resp_sender, resp_receiver) = sync_channel::<Resp>(64);
779
780 player.cmd_channel = cmd_sender;
781
782 let _warnings = player
783 .play(
784 1,
785 None,
786 Rhythm::new(2, 4),
787 Key::SHARP_1,
788 ¬e_repo,
789 &Store::new(false),
790 &Store::new(false),
791 &Store::new(false),
792 &Store::new(false),
793 )
794 .unwrap();
795
796 let mut state = PlayState::Init;
797 let ctrl = Player::perform_cmd(&cmd_receiver, &mut state, &resp_sender);
798 assert_eq!(ctrl, jack::Control::Continue);
799
800 match &state {
801 PlayState::Init => panic!("Should playing."),
802 PlayState::Playing {
803 seq,
804 current_loc,
805 play_data,
806 } => {
807 assert_eq!(*seq, 1);
808 assert_eq!(*current_loc, 0);
809 let midi_data0: Vec<&(u64, Vec<Vec<u8>>)> = play_data0.midi_data.iter().collect();
810 let midi_data1: Vec<&(u64, Vec<Vec<u8>>)> = play_data.midi_data.iter().collect();
811 assert_eq!(midi_data0, midi_data1);
812 },
813 PlayState::Stopping => panic!("Should playing.")
814 }
815 let resp = resp_receiver.recv().unwrap();
816 assert_eq!(resp, Resp::Ok { seq: 1 });
817
818 let _warnings = player
820 .play(
821 2,
822 None,
823 Rhythm::new(2, 4),
824 Key::SHARP_1,
825 ¬e_repo,
826 &Store::new(false),
827 &Store::new(false),
828 &Store::new(false),
829 &Store::new(false),
830 )
831 .unwrap();
832
833 let ctrl = Player::perform_cmd(&cmd_receiver, &mut state, &resp_sender);
834 assert_eq!(ctrl, jack::Control::Continue);
835
836 match &state {
837 PlayState::Init => panic!("Should playing."),
838 PlayState::Playing {
839 seq,
840 current_loc,
841 play_data,
842 } => {
843 assert_eq!(*seq, 1);
844 assert_eq!(*current_loc, 0);
845 let midi_data0: Vec<&(u64, Vec<Vec<u8>>)> = play_data0.midi_data.iter().collect();
846 let midi_data1: Vec<&(u64, Vec<Vec<u8>>)> = play_data.midi_data.iter().collect();
847 assert_eq!(midi_data0, midi_data1);
848 },
849 PlayState::Stopping => panic!("Should playing.")
850 }
851 let resp = resp_receiver.recv().unwrap();
852 assert_eq!(
853 resp,
854 Resp::Err {
855 seq: Some(2),
856 error: CmdError::AlreadyPlaying
857 }
858 );
859
860 player.stop(3).unwrap();
862 let ctrl = Player::perform_cmd(&cmd_receiver, &mut state, &resp_sender);
863 assert_eq!(ctrl, jack::Control::Continue);
864
865 match &state {
866 PlayState::Init => {}
867 PlayState::Playing {
868 seq: _,
869 current_loc: _,
870 play_data: _,
871 } => panic!("Should init"),
872 PlayState::Stopping => {}
873 }
874 let resp = resp_receiver.recv().unwrap();
875 assert_eq!(
876 resp,
877 Resp::Info {
878 seq: Some(3),
879 info: CmdInfo::PlayingEnded
880 }
881 );
882
883 player.stop(4).unwrap();
885 let ctrl = Player::perform_cmd(&cmd_receiver, &mut state, &resp_sender);
886 assert_eq!(ctrl, jack::Control::Continue);
887
888 match &state {
889 PlayState::Init => {}
890 PlayState::Playing {
891 seq: _,
892 current_loc: _,
893 play_data: _,
894 } => panic!("Should init"),
895 PlayState::Stopping => {}
896 }
897 let resp = resp_receiver.recv().unwrap();
898 assert_eq!(resp, Resp::Ok { seq: 4 });
899
900 drop(player);
901 let ctrl = Player::perform_cmd(&cmd_receiver, &mut state, &resp_sender);
902 assert_eq!(ctrl, jack::Control::Quit);
903 }
904}