1use super::PlayPosition;
32use crate::disc_base::shift_lba;
33use crate::periph::Cd;
34use psx_hw::cd::xa::{END_GUARD_SECTORS, SECTORS_PER_SECOND};
35use psx_hw::cd::{CMD_READS, CMD_SETFILTER, MODE_DOUBLE_SPEED, MODE_XA_ADPCM, MODE_XA_FILTER};
36
37pub const COMMAND_SPINS: u32 = 131_072;
39const PAUSE_SPINS: u32 = 2_000_000;
42
43#[derive(Copy, Clone, Debug, PartialEq, Eq)]
45pub enum DriveSpeed {
46 Single,
48 Double,
50}
51
52impl DriveSpeed {
53 const fn sectors_per_second(self) -> u32 {
54 match self {
55 DriveSpeed::Single => SECTORS_PER_SECOND,
56 DriveSpeed::Double => 2 * SECTORS_PER_SECOND,
57 }
58 }
59
60 const fn mode(self) -> u8 {
61 let speed = match self {
62 DriveSpeed::Single => 0,
63 DriveSpeed::Double => MODE_DOUBLE_SPEED,
64 };
65 MODE_XA_ADPCM | MODE_XA_FILTER | speed
66 }
67}
68
69#[derive(Copy, Clone, Debug, PartialEq, Eq)]
71pub struct File {
72 lba: u32,
73 sector_count: u32,
74 number: u8,
75 speed: DriveSpeed,
76}
77
78impl File {
79 pub const fn new(lba: u32, sector_count: u32, number: u8, speed: DriveSpeed) -> Self {
84 Self {
85 lba,
86 sector_count,
87 number,
88 speed,
89 }
90 }
91
92 pub const fn from_directory_entry(
95 lba: u32,
96 size_bytes: u32,
97 number: u8,
98 speed: DriveSpeed,
99 ) -> Self {
100 Self::new(lba, size_bytes / 2048, number, speed)
101 }
102
103 pub const fn song(self, channel: u8) -> Song {
105 Song {
106 file: self,
107 channel,
108 }
109 }
110
111 pub const fn span_sector_count(self) -> u32 {
113 self.sector_count.saturating_sub(END_GUARD_SECTORS)
114 }
115}
116
117#[derive(Copy, Clone, Debug, PartialEq, Eq)]
119pub struct Song {
120 file: File,
121 channel: u8,
122}
123
124#[derive(Copy, Clone, Debug, PartialEq, Eq)]
126pub enum Error {
127 Unmute,
129 SetMode,
131 SetFilter,
133 Seek,
135 Read,
137}
138
139#[derive(Copy, Clone, Debug, PartialEq, Eq)]
141pub enum Event {
142 Idle,
144 Playing,
146 Looped,
148 Finished,
150}
151
152trait Drive {
155 fn unmute(&mut self) -> bool;
156 fn set_mode(&mut self, mode: u8) -> bool;
157 fn set_filter(&mut self, file: u8, channel: u8) -> bool;
158 fn set_target(&mut self, lba: u32) -> bool;
159 fn start_streaming(&mut self) -> bool;
160 fn pause(&mut self) -> bool;
163 fn head_lba(&mut self) -> Option<u32>;
165}
166
167impl Drive for Cd {
168 fn unmute(&mut self) -> bool {
169 self.try_unmute(COMMAND_SPINS).is_some()
170 }
171 fn set_mode(&mut self, mode: u8) -> bool {
172 self.try_set_mode(mode, COMMAND_SPINS).is_some()
173 }
174 fn set_filter(&mut self, file: u8, channel: u8) -> bool {
175 self.try_command(CMD_SETFILTER, &[file, channel], COMMAND_SPINS)
176 .is_some()
177 }
178 fn set_target(&mut self, lba: u32) -> bool {
179 self.try_set_target_lba(lba, COMMAND_SPINS).is_some()
180 }
181 fn start_streaming(&mut self) -> bool {
182 self.try_command(CMD_READS, &[], COMMAND_SPINS).is_some()
183 }
184 fn pause(&mut self) -> bool {
185 self.try_pause_until_complete(PAUSE_SPINS)
186 }
187 fn head_lba(&mut self) -> Option<u32> {
188 let position = PlayPosition::parse(&self.try_play_position(COMMAND_SPINS)?)?;
189 let frames = (position.absolute_min as u32 * 60 + position.absolute_sec as u32) * 75
190 + position.absolute_frame as u32;
191 frames.checked_sub(150)
193 }
194}
195
196#[derive(Copy, Clone, Debug, PartialEq, Eq)]
197enum State {
198 Idle,
199 Seeking,
203 Streaming,
204}
205
206struct Engine {
207 state: State,
208 song: Option<Song>,
209 looping: bool,
210 pause_pending: bool,
212 start_lba: u32,
213 head_lba: u32,
214}
215
216impl Engine {
217 const fn new() -> Self {
218 Self {
219 state: State::Idle,
220 song: None,
221 looping: false,
222 pause_pending: false,
223 start_lba: 0,
224 head_lba: 0,
225 }
226 }
227
228 fn start(&mut self, drive: &mut impl Drive, song: Song, looping: bool) -> Result<(), Error> {
229 let file = song.file;
230 let result = (|| {
231 if !drive.unmute() {
232 return Err(Error::Unmute);
233 }
234 if !drive.set_mode(file.speed.mode()) {
235 return Err(Error::SetMode);
236 }
237 if !drive.set_filter(file.number, song.channel) {
238 return Err(Error::SetFilter);
239 }
240 self.begin_read(drive, song)
241 })();
242 if result.is_ok() {
243 self.song = Some(song);
244 self.looping = looping;
245 } else {
246 self.state = State::Idle;
247 self.song = None;
248 }
249 result
250 }
251
252 fn begin_read(&mut self, drive: &mut impl Drive, song: Song) -> Result<(), Error> {
254 let start = shift_lba(song.file.lba);
255 if !drive.set_target(start) {
256 return Err(Error::Seek);
257 }
258 if !drive.start_streaming() {
259 return Err(Error::Read);
260 }
261 self.start_lba = start;
262 self.head_lba = start;
263 self.pause_pending = false;
264 self.state = State::Seeking;
265 Ok(())
266 }
267
268 fn stop(&mut self, drive: &mut impl Drive) {
271 if self.state != State::Idle {
272 self.pause_pending = !drive.pause();
273 }
274 self.state = State::Idle;
275 self.song = None;
276 }
277
278 fn poll(&mut self, drive: &mut impl Drive) -> Event {
279 let Some(song) = self.song else {
280 if self.pause_pending {
281 self.pause_pending = !drive.pause();
282 }
283 return Event::Idle;
284 };
285 let end = self.start_lba + song.file.span_sector_count();
286 if let Some(head) = drive.head_lba() {
287 if self.state == State::Seeking && (self.start_lba..end).contains(&head) {
288 self.state = State::Streaming;
289 }
290 if self.state == State::Streaming {
291 self.head_lba = head;
292 }
293 if self.state == State::Streaming && head >= end {
294 return self.reach_end(drive, song);
295 }
296 }
297 Event::Playing
298 }
299
300 fn is_streaming(&self) -> bool {
301 self.state == State::Streaming
302 }
303
304 fn elapsed_millis(&self) -> u32 {
305 let Some(song) = self.song else {
306 return 0;
307 };
308 let sectors = self.head_lba.saturating_sub(self.start_lba);
309 sectors * 1000 / song.file.speed.sectors_per_second()
310 }
311
312 fn reach_end(&mut self, drive: &mut impl Drive, song: Song) -> Event {
313 if self.looping && self.begin_read(drive, song).is_ok() {
314 return Event::Looped;
315 }
316 self.stop(drive);
317 Event::Finished
318 }
319}
320
321pub struct Player {
324 cd: Cd,
325 engine: Engine,
326}
327
328impl Player {
329 pub const fn new(cd: Cd) -> Self {
333 Self {
334 cd,
335 engine: Engine::new(),
336 }
337 }
338
339 pub fn release(mut self) -> Cd {
341 self.stop();
342 self.cd
343 }
344
345 pub fn play(&mut self, song: Song, looping: bool) -> Result<(), Error> {
349 self.engine.start(&mut self.cd, song, looping)
350 }
351
352 pub fn stop(&mut self) {
354 self.engine.stop(&mut self.cd);
355 }
356
357 pub fn poll(&mut self) -> Event {
360 self.engine.poll(&mut self.cd)
361 }
362
363 pub fn is_playing(&self) -> bool {
365 self.engine.state != State::Idle
366 }
367
368 pub fn is_streaming(&self) -> bool {
371 self.engine.is_streaming()
372 }
373
374 pub fn song(&self) -> Option<Song> {
376 self.engine.song
377 }
378
379 pub fn elapsed_millis(&self) -> u32 {
382 self.engine.elapsed_millis()
383 }
384
385 pub fn set_volume(&mut self, left: u8, right: u8) {
389 self.cd.set_audio_mixer(left, 0, right, 0);
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 extern crate std;
396 use super::*;
397 use std::string::{String, ToString};
398 use std::vec::Vec;
399 use std::{format, vec};
400
401 #[test]
402 fn an_idle_player_owns_the_token_and_hands_it_back() {
403 let player = Player::new(unsafe { Cd::steal() });
406 assert!(!player.is_playing());
407 let _token: Cd = player.release();
408 }
409
410 #[derive(Default)]
412 struct Fake {
413 log: Vec<String>,
414 fail_at: Option<&'static str>,
415 heads: Vec<Option<u32>>,
416 pause_refusals: u32,
417 }
418
419 impl Fake {
420 fn step(&mut self, name: &str) -> bool {
421 self.log.push(name.to_string());
422 self.fail_at
423 != Some(match name {
424 n if n.starts_with("unmute") => "unmute",
425 n if n.starts_with("mode") => "mode",
426 n if n.starts_with("filter") => "filter",
427 n if n.starts_with("target") => "target",
428 _ => "read",
429 })
430 }
431 }
432
433 impl Drive for Fake {
434 fn unmute(&mut self) -> bool {
435 self.step("unmute")
436 }
437 fn set_mode(&mut self, mode: u8) -> bool {
438 self.step(&format!("mode {mode:#04x}"))
439 }
440 fn set_filter(&mut self, file: u8, channel: u8) -> bool {
441 self.step(&format!("filter {file} {channel}"))
442 }
443 fn set_target(&mut self, lba: u32) -> bool {
444 self.step(&format!("target {lba}"))
445 }
446 fn start_streaming(&mut self) -> bool {
447 self.step("read")
448 }
449 fn pause(&mut self) -> bool {
450 self.log.push("pause".into());
451 if self.pause_refusals > 0 {
452 self.pause_refusals -= 1;
453 return false;
454 }
455 true
456 }
457 fn head_lba(&mut self) -> Option<u32> {
458 if self.heads.is_empty() {
459 None
460 } else {
461 self.heads.remove(0)
462 }
463 }
464 }
465
466 fn song(speed: DriveSpeed) -> Song {
468 File::new(1000, 100 + END_GUARD_SECTORS, 3, speed).song(2)
469 }
470
471 #[test]
472 fn start_sets_up_the_filter_before_the_read() {
473 let mut drive = Fake::default();
474 let mut engine = Engine::new();
475 engine
476 .start(&mut drive, song(DriveSpeed::Single), true)
477 .unwrap();
478 assert_eq!(
479 drive.log,
480 ["unmute", "mode 0x48", "filter 3 2", "target 1000", "read"]
481 );
482 let mut drive = Fake::default();
483 engine
484 .start(&mut drive, song(DriveSpeed::Double), true)
485 .unwrap();
486 assert_eq!(drive.log[1], "mode 0xc8");
487 }
488
489 #[test]
490 fn each_refused_step_is_reported_and_leaves_the_player_idle() {
491 for (at, error) in [
492 ("unmute", Error::Unmute),
493 ("mode", Error::SetMode),
494 ("filter", Error::SetFilter),
495 ("target", Error::Seek),
496 ("read", Error::Read),
497 ] {
498 let mut drive = Fake {
499 fail_at: Some(at),
500 ..Fake::default()
501 };
502 let mut engine = Engine::new();
503 let result = engine.start(&mut drive, song(DriveSpeed::Single), false);
504 assert_eq!(result, Err(error));
505 assert_eq!(engine.poll(&mut drive), Event::Idle);
506 }
507 }
508
509 #[test]
510 fn an_old_head_position_does_not_end_a_fresh_start() {
511 let mut drive = Fake::default();
512 let mut engine = Engine::new();
513 engine
514 .start(&mut drive, song(DriveSpeed::Double), true)
515 .unwrap();
516 drive.log.clear();
517 drive.heads = vec![Some(1105), None, Some(1003), Some(1050)];
519 assert_eq!(engine.poll(&mut drive), Event::Playing);
520 assert_eq!(engine.poll(&mut drive), Event::Playing);
521 assert!(!engine.is_streaming());
522 assert_eq!(engine.poll(&mut drive), Event::Playing);
523 assert!(engine.is_streaming());
524 assert_eq!(engine.elapsed_millis(), 3 * 1000 / 150);
525 assert_eq!(engine.poll(&mut drive), Event::Playing);
526 assert_eq!(engine.elapsed_millis(), 50 * 1000 / 150);
527 assert!(
528 drive.log.is_empty(),
529 "no command beyond GetlocP: {:?}",
530 drive.log
531 );
532 }
533
534 #[test]
535 fn a_loop_restarts_at_the_top_and_waits_for_the_head_again() {
536 let mut drive = Fake::default();
537 let mut engine = Engine::new();
538 engine
539 .start(&mut drive, song(DriveSpeed::Single), true)
540 .unwrap();
541 drive.log.clear();
542 drive.heads = vec![Some(1010), Some(1100), Some(1103), Some(1020)];
543 assert_eq!(engine.poll(&mut drive), Event::Playing);
544 assert_eq!(engine.poll(&mut drive), Event::Looped);
545 assert_eq!(drive.log, ["target 1000", "read"]);
546 assert_eq!(engine.poll(&mut drive), Event::Playing);
548 assert_eq!(drive.log.len(), 2);
549 assert_eq!(engine.poll(&mut drive), Event::Playing);
550 assert!(engine.is_streaming());
551 }
552
553 #[test]
554 fn a_one_shot_pauses_at_the_end_and_goes_idle() {
555 let mut drive = Fake::default();
556 let mut engine = Engine::new();
557 engine
558 .start(&mut drive, song(DriveSpeed::Single), false)
559 .unwrap();
560 drive.log.clear();
561 drive.heads = vec![Some(1099), Some(1101)];
562 assert_eq!(engine.poll(&mut drive), Event::Playing);
563 assert_eq!(engine.poll(&mut drive), Event::Finished);
564 assert_eq!(drive.log, ["pause"]);
565 assert_eq!(engine.poll(&mut drive), Event::Idle);
566 }
567
568 #[test]
569 fn stop_pauses_only_a_running_player() {
570 let mut drive = Fake::default();
571 let mut engine = Engine::new();
572 engine.stop(&mut drive);
573 assert!(drive.log.is_empty());
574 engine
575 .start(&mut drive, song(DriveSpeed::Single), false)
576 .unwrap();
577 engine.stop(&mut drive);
578 assert_eq!(drive.log.last().unwrap(), "pause");
579 assert_eq!(engine.poll(&mut drive), Event::Idle);
580 }
581
582 #[test]
583 fn a_refused_pause_is_retried_at_the_next_polls() {
584 let mut drive = Fake {
585 pause_refusals: 2,
586 ..Fake::default()
587 };
588 let mut engine = Engine::new();
589 engine
590 .start(&mut drive, song(DriveSpeed::Single), false)
591 .unwrap();
592 drive.log.clear();
593 engine.stop(&mut drive);
594 assert_eq!(engine.poll(&mut drive), Event::Idle);
595 assert_eq!(engine.poll(&mut drive), Event::Idle);
596 assert_eq!(drive.log, ["pause", "pause", "pause"]);
597 assert_eq!(engine.poll(&mut drive), Event::Idle);
599 assert_eq!(drive.log.len(), 3);
600 }
601
602 #[test]
603 fn starting_a_song_cancels_a_pending_pause() {
604 let mut drive = Fake {
605 pause_refusals: 5,
606 ..Fake::default()
607 };
608 let mut engine = Engine::new();
609 engine
610 .start(&mut drive, song(DriveSpeed::Single), false)
611 .unwrap();
612 engine.stop(&mut drive);
613 engine
614 .start(&mut drive, song(DriveSpeed::Single), false)
615 .unwrap();
616 drive.log.clear();
617 assert_eq!(engine.poll(&mut drive), Event::Playing);
618 assert!(drive.log.is_empty());
619 }
620
621 #[test]
622 fn a_failed_loop_restart_finishes_the_song() {
623 let mut drive = Fake::default();
624 let mut engine = Engine::new();
625 engine
626 .start(&mut drive, song(DriveSpeed::Single), true)
627 .unwrap();
628 drive.heads = vec![Some(1010), Some(1100)];
629 drive.fail_at = Some("target");
630 assert_eq!(engine.poll(&mut drive), Event::Playing);
631 assert_eq!(engine.poll(&mut drive), Event::Finished);
632 assert_eq!(drive.log.last().unwrap(), "pause");
633 }
634
635 #[test]
636 fn the_file_span_excludes_the_guard() {
637 let file = File::from_directory_entry(7, 2048 * 116, 1, DriveSpeed::Single);
638 assert_eq!(file.span_sector_count(), 100);
639 assert_eq!(
640 File::new(0, 4, 0, DriveSpeed::Double).span_sector_count(),
641 0
642 );
643 }
644}