返回 CodeWhale
audio_cursor.rs
根目录 / crates / tui / src / tui / pet_watch / audio_cursor.rs
1 use super::audio::{self, Target};
2 /// Presentation retains only the voices that can still contribute samples.
3 /// Their identities, timestamps and PCM all come from the existing JS core.
4 pub(super) struct AudioCursor {
5 pub(super) target: Target,
6 pub(super) sample: usize,
7 pub(super) voices: Vec<serde_json::Value>,
8 }
9
10 impl AudioCursor {
11 pub(super) fn new(target: Target, time_ms: f64) -> Self {
12 Self {
13 target,
14 sample: (time_ms * audio::SAMPLE_RATE as f64 / 1000.0).floor() as usize,
15 voices: Vec::new(),
16 }
17 }
18 pub(super) fn present(
19 &mut self,
20 ctx: &rquickjs::Ctx<'_>,
21 target: &Target,
22 time_ms: f64,
23 ) -> rquickjs::Result<()> {
24 if !self.target.same_stream(target) {
25 self.target = target.clone();
26 self.sample = (time_ms * audio::SAMPLE_RATE as f64 / 1000.0).floor() as usize;
27 self.voices.clear();
28 }
29 if !target.current() {
30 self.sample = (time_ms * audio::SAMPLE_RATE as f64 / 1000.0).floor() as usize;
31 self.voices.clear();
32 return Ok(());
33 }
34 if let Some(channels) = self.render_samples(ctx, time_ms)? {
35 target
36 .send(channels)
37 .map_err(|_| rquickjs::Error::Unknown)?;
38 }
39 Ok(())
40 }
41
42 pub(super) fn render_samples(
43 &mut self,
44 ctx: &rquickjs::Ctx<'_>,
45 time_ms: f64,
46 ) -> rquickjs::Result<Option<[Vec<f32>; 2]>> {
47 let end = (time_ms * audio::SAMPLE_RATE as f64 / 1000.0).floor() as usize;
48 // A pause, new output or delayed catch-up cannot play historical sound.
49 if end < self.sample || end - self.sample > audio::MAX_FRAMES {
50 self.sample = end;
51 self.voices.clear();
52 }
53 let json: String = ctx.eval("JSON.stringify(JSON.parse(pet.snapshot()).voices)")?;
54 let voices: Vec<serde_json::Value> =
55 serde_json::from_str(&json).map_err(|_| rquickjs::Error::Unknown)?;
56 let start = self.sample as f64 / audio::SAMPLE_RATE as f64;
57 self.voices.extend(voices);
58 self.voices.retain(|v| {
59 v["start"]
60 .as_f64()
61 .zip(v["duration"].as_f64())
62 .is_some_and(|(at, duration)| at + duration > start)
63 });
64 if self.voices.len() > 128 {
65 return Err(rquickjs::Error::Unknown);
66 }
67 if end == self.sample {
68 return Ok(None);
69 }
70 ctx.globals().set(
71 "petAudioVoices",
72 serde_json::to_string(&self.voices).map_err(|_| rquickjs::Error::Unknown)?,
73 )?;
74 ctx.globals().set("petAudioStart", self.sample)?;
75 ctx.globals().set("petAudioLength", end - self.sample)?;
76 let json: String =
77 ctx.eval("pet.pcm(petAudioVoices, petAudioStart, petAudioLength, 48000)")?;
78 let channels: [Vec<f32>; 2] =
79 serde_json::from_str(&json).map_err(|_| rquickjs::Error::Unknown)?;
80 if channels[0].len() != end - self.sample || channels[1].len() != end - self.sample {
81 return Err(rquickjs::Error::Unknown);
82 }
83 self.sample = end;
84 Ok(Some(channels))
85 }
86 }
87
87 lines RUST