1use std::collections::VecDeque;
8use std::error::Error;
9use std::fmt;
10use std::ops::Range;
11
12use aede_dsp::{Dsp, DspError, PcmFormat, ProcessStats, RateConverter, RateError, ToneControls};
13
14#[derive(Debug)]
16pub enum SessionError {
17 Dsp(DspError),
19 Resample(RateError),
21 TrackActive,
23 NoTrack,
25 Finished,
27 FrameCountOverflow,
29 OutputTimeline,
31}
32
33impl fmt::Display for SessionError {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::Dsp(error) => write!(f, "{error}"),
37 Self::Resample(error) => write!(f, "{error}"),
38 Self::TrackActive => f.write_str("seal the active playback track first"),
39 Self::NoTrack => f.write_str("no active playback track"),
40 Self::Finished => f.write_str("continuous playback session has finished"),
41 Self::FrameCountOverflow => f.write_str("continuous playback frame count overflowed"),
42 Self::OutputTimeline => {
43 f.write_str("converted PCM exceeded its playback track timeline")
44 }
45 }
46 }
47}
48
49impl Error for SessionError {
50 fn source(&self) -> Option<&(dyn Error + 'static)> {
51 match self {
52 Self::Dsp(error) => Some(error),
53 Self::Resample(error) => Some(error),
54 _ => None,
55 }
56 }
57}
58
59impl From<DspError> for SessionError {
60 fn from(error: DspError) -> Self {
61 Self::Dsp(error)
62 }
63}
64
65impl From<RateError> for SessionError {
66 fn from(error: RateError) -> Self {
67 Self::Resample(error)
68 }
69}
70
71#[derive(Clone, Debug, PartialEq)]
73pub struct TrackSpan {
74 pub token: usize,
76 pub samples: Range<usize>,
79 pub stats: ProcessStats,
82 pub complete: bool,
86}
87
88pub struct SessionBlock<'a> {
90 pub samples: &'a [f32],
92 pub f32le: &'a [u8],
94 pub spans: &'a [TrackSpan],
97}
98
99struct PendingTrack {
100 token: usize,
101 gain_db: f32,
102 end_frame: Option<u64>,
103}
104
105pub struct PcmSession {
120 input: PcmFormat,
121 output: PcmFormat,
122 tone: ToneControls,
123 dsp: Dsp,
124 converter: Option<RateConverter>,
125 tracks: VecDeque<PendingTrack>,
126 active: bool,
127 source_frames: u64,
128 output_frames: u64,
129 finished: bool,
130 samples: Vec<f32>,
131 bytes: Vec<u8>,
132 spans: Vec<TrackSpan>,
133}
134
135impl PcmSession {
136 pub fn new(
139 input: PcmFormat,
140 output_rate: u32,
141 tone: ToneControls,
142 ) -> Result<Self, SessionError> {
143 let output = PcmFormat::with_layout(output_rate, input.layout())?;
144 let converter = if input.sample_rate() == output_rate {
145 None
146 } else {
147 Some(RateConverter::new(input, output_rate)?)
148 };
149 let mut dsp = Dsp::new(output);
150 dsp.set_tone(tone)?;
151 Ok(Self {
152 input,
153 output,
154 tone,
155 dsp,
156 converter,
157 tracks: VecDeque::new(),
158 active: false,
159 source_frames: 0,
160 output_frames: 0,
161 finished: false,
162 samples: Vec::new(),
163 bytes: Vec::new(),
164 spans: Vec::new(),
165 })
166 }
167
168 pub fn input_format(&self) -> PcmFormat {
170 self.input
171 }
172
173 pub fn output_format(&self) -> PcmFormat {
175 self.output
176 }
177
178 pub fn compatible(&self, input: PcmFormat, output_rate: u32, tone: ToneControls) -> bool {
181 !self.finished
182 && input == self.input
183 && output_rate == self.output.sample_rate()
184 && tone == self.tone
185 }
186
187 pub fn begin_track(&mut self, token: usize, gain_db: f32) -> Result<(), SessionError> {
191 if self.finished {
192 return Err(SessionError::Finished);
193 }
194 if self.active {
195 return Err(SessionError::TrackActive);
196 }
197 let mut validation = Dsp::new(self.output);
198 validation.set_gain_db(gain_db, 0)?;
199 self.tracks.push_back(PendingTrack {
200 token,
201 gain_db,
202 end_frame: None,
203 });
204 self.active = true;
205 Ok(())
206 }
207
208 pub fn push_source(&mut self, samples: &[f32]) -> Result<SessionBlock<'_>, SessionError> {
211 if self.finished {
212 return Err(SessionError::Finished);
213 }
214 if !self.active {
215 return Err(SessionError::NoTrack);
216 }
217 let channels = usize::from(self.input.channels());
218 if !samples.len().is_multiple_of(channels) {
219 return Err(DspError::IncompleteFrame.into());
220 }
221 if samples.iter().any(|sample| !sample.is_finite()) {
222 return Err(DspError::NonFiniteSample.into());
223 }
224 let frames = u64::try_from(samples.len() / channels)
225 .map_err(|_| SessionError::FrameCountOverflow)?;
226 let source_frames = self
227 .source_frames
228 .checked_add(frames)
229 .ok_or(SessionError::FrameCountOverflow)?;
230 self.output_boundary(source_frames)?;
231 self.clear_block();
232 if let Some(converter) = &mut self.converter {
233 self.samples
234 .extend_from_slice(converter.push(samples, false)?);
235 } else {
236 self.samples.extend_from_slice(samples);
237 }
238 self.source_frames = source_frames;
239 self.process_block()?;
240 Ok(self.block())
241 }
242
243 pub fn end_track(&mut self) -> Result<SessionBlock<'_>, SessionError> {
247 if self.finished {
248 return Err(SessionError::Finished);
249 }
250 if !self.active {
251 return Err(SessionError::NoTrack);
252 }
253 let end_frame = self.output_boundary(self.source_frames)?;
254 let track = self.tracks.back_mut().ok_or(SessionError::OutputTimeline)?;
255 track.end_frame = Some(end_frame);
256 self.active = false;
257 self.clear_block();
258 self.complete_ready_tracks(0)?;
259 Ok(self.block())
260 }
261
262 pub fn finish(&mut self) -> Result<SessionBlock<'_>, SessionError> {
265 if self.active {
266 return Err(SessionError::TrackActive);
267 }
268 self.clear_block();
269 if self.finished {
270 return Ok(self.block());
271 }
272 if let Some(converter) = &mut self.converter {
273 self.samples.extend_from_slice(converter.push(&[], true)?);
274 }
275 self.process_block()?;
276 if !self.tracks.is_empty()
277 || self.output_frames != self.output_boundary(self.source_frames)?
278 {
279 return Err(SessionError::OutputTimeline);
280 }
281 self.finished = true;
282 Ok(self.block())
283 }
284
285 fn output_boundary(&self, frames: u64) -> Result<u64, SessionError> {
286 let boundary = (u128::from(frames) * u128::from(self.output.sample_rate()))
287 .div_ceil(u128::from(self.input.sample_rate()));
288 u64::try_from(boundary).map_err(|_| SessionError::FrameCountOverflow)
289 }
290
291 fn clear_block(&mut self) {
292 self.samples.clear();
293 self.bytes.clear();
294 self.spans.clear();
295 }
296
297 fn block(&self) -> SessionBlock<'_> {
298 SessionBlock {
299 samples: &self.samples,
300 f32le: &self.bytes,
301 spans: &self.spans,
302 }
303 }
304
305 fn complete_ready_tracks(&mut self, sample_index: usize) -> Result<(), SessionError> {
306 while let Some(track) = self.tracks.front() {
307 let Some(end) = track.end_frame else {
308 break;
309 };
310 if end < self.output_frames {
311 return Err(SessionError::OutputTimeline);
312 }
313 if end != self.output_frames {
314 break;
315 }
316 self.spans.push(TrackSpan {
317 token: track.token,
318 samples: sample_index..sample_index,
319 stats: ProcessStats::default(),
320 complete: true,
321 });
322 self.tracks.pop_front();
323 }
324 Ok(())
325 }
326
327 fn process_block(&mut self) -> Result<(), SessionError> {
328 let channels = usize::from(self.output.channels());
329 if !self.samples.len().is_multiple_of(channels) {
330 return Err(SessionError::OutputTimeline);
331 }
332 let current_boundary = self.output_boundary(self.source_frames)?;
333 let mut offset = 0;
334 self.complete_ready_tracks(offset)?;
335 while offset < self.samples.len() {
336 let track = self.tracks.front().ok_or(SessionError::OutputTimeline)?;
337 let boundary = track.end_frame.unwrap_or(current_boundary);
338 let available = boundary
339 .checked_sub(self.output_frames)
340 .ok_or(SessionError::OutputTimeline)?;
341 let frames = (self.samples.len() - offset) / channels;
342 let accepted = frames.min(usize::try_from(available).unwrap_or(usize::MAX));
343 if accepted == 0 {
344 return Err(SessionError::OutputTimeline);
345 }
346 let end = offset + accepted * channels;
347 let token = track.token;
348 let gain_db = track.gain_db;
349 let sealed = track.end_frame.is_some();
350 self.dsp.set_gain_db(gain_db, 0)?;
351 let stats = self
352 .dsp
353 .process_for_output(&mut self.samples[offset..end])?;
354 self.output_frames = self
355 .output_frames
356 .checked_add(accepted as u64)
357 .ok_or(SessionError::FrameCountOverflow)?;
358 let complete = sealed && self.output_frames == boundary;
359 self.spans.push(TrackSpan {
360 token,
361 samples: offset..end,
362 stats,
363 complete,
364 });
365 if complete {
366 self.tracks.pop_front();
367 }
368 offset = end;
369 self.complete_ready_tracks(offset)?;
370 }
371 self.bytes.reserve(self.samples.len() * 4);
372 for sample in &self.samples {
373 self.bytes.extend_from_slice(&sample.to_le_bytes());
374 }
375 Ok(())
376 }
377}
378
379#[cfg(test)]
380#[path = "session_tests.rs"]
381mod tests;