use std::{
fmt::Debug,
num::{NonZero, NonZeroU16},
time::Duration,
};
use simple_left_right::{WriteGuard, Writer};
use crate::{
audio_processing::playback::PlaybackStatus,
live_audio::LiveAudio,
project::{
note_event::NoteEvent,
song::{Song, SongOperation, ValidOperation},
},
sample::Sample,
};
#[derive(Debug, Clone, Copy)]
pub enum ToWorkerMsg {
Playback(PlaybackSettings),
StopPlayback,
PlayEvent(NoteEvent),
StopLiveNote,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use]
pub enum SendResult {
Success,
BufferFull,
AudioInactive,
}
impl SendResult {
#[track_caller]
pub fn unwrap(self) {
match self {
SendResult::Success => (),
SendResult::BufferFull => panic!("Buffer full"),
SendResult::AudioInactive => panic!("Audio inactive"),
}
}
pub fn is_success(self) -> bool {
self == Self::Success
}
}
#[derive(Debug)]
struct ActiveStreamComms {
buffer_time: Duration,
send: rtrb::Producer<ToWorkerMsg>,
status: triple_buffer::Output<Option<PlaybackStatus>>,
}
#[derive(Debug, Default)]
pub(crate) struct Collector {
samples: Vec<Sample>,
}
impl Collector {
pub fn add_sample(&mut self, sample: Sample) {
self.samples.push(sample);
}
fn collect(&mut self) {
self.samples.retain(|s| {
s.strongcount() != 1
});
}
}
pub struct AudioManager {
song: Writer<Song, ValidOperation>,
gc: Collector,
stream_comms: Option<ActiveStreamComms>,
}
impl AudioManager {
pub fn new(song: Song) -> Self {
let mut gc = Collector::default();
for (_, sample) in song.samples.iter().flatten() {
gc.add_sample(sample.clone());
}
let left_right = simple_left_right::Writer::new(song);
Self {
song: left_right,
gc,
stream_comms: None,
}
}
pub fn try_edit_song(&mut self) -> Option<SongEdit<'_>> {
self.song.try_lock().map(|song| SongEdit {
song,
gc: &mut self.gc,
})
}
pub fn get_song(&self) -> &Song {
self.song.read()
}
pub fn collect_garbage(&mut self) {
self.gc.collect();
}
pub fn try_msg_worker(&mut self, msg: ToWorkerMsg) -> SendResult {
if let Some(stream) = &mut self.stream_comms {
match stream.send.push(msg) {
Ok(_) => SendResult::Success,
Err(_) => SendResult::BufferFull,
}
} else {
SendResult::AudioInactive
}
}
pub fn playback_status(&mut self) -> Option<&Option<PlaybackStatus>> {
self.stream_comms.as_mut().map(|s| s.status.read())
}
pub fn buffer_time(&self) -> Option<Duration> {
self.stream_comms.as_ref().map(|s| s.buffer_time)
}
pub fn get_callback<Sample: dasp::sample::Sample + dasp::sample::FromSample<f32>>(
&mut self,
config: OutputConfig,
) -> impl FnMut(&mut [Sample]) {
const TO_WORKER_CAPACITY: usize = 5;
assert!(self.stream_comms.is_none(), "Stream already active");
let from_worker = triple_buffer::triple_buffer(&None);
let to_worker = rtrb::RingBuffer::new(TO_WORKER_CAPACITY);
let reader = self.song.build_reader().unwrap();
let audio_worker = LiveAudio::new(reader, to_worker.1, from_worker.0, config);
let buffer_time =
Duration::from_millis((config.buffer_size * 1000 / config.sample_rate).into());
self.stream_comms = Some(ActiveStreamComms {
buffer_time,
send: to_worker.0,
status: from_worker.1,
});
audio_worker.get_typed_callback()
}
pub fn stream_closed(&mut self) {
self.stream_comms = None
}
}
impl Drop for AudioManager {
fn drop(&mut self) {
if let Some(stream) = &mut self.stream_comms {
eprintln!("AudioManager dropped while audio Stream still active.");
let msg1 = stream.send.push(ToWorkerMsg::StopLiveNote);
let msg2 = stream.send.push(ToWorkerMsg::StopPlayback);
if msg1.is_err() || msg2.is_err() {
eprintln!("Audio playback couldn't be stopped completely");
} else {
eprintln!("Audio playback was stopped");
}
}
}
}
#[derive(Debug)]
pub struct SongEdit<'a> {
song: WriteGuard<'a, Song, ValidOperation>,
gc: &'a mut Collector,
}
impl SongEdit<'_> {
pub fn apply_operation(&mut self, op: SongOperation) -> Result<(), SongOperation> {
let valid_operation = ValidOperation::new(op, self.gc, self.song.read())?;
self.song.apply_op(valid_operation);
Ok(())
}
pub fn song(&self) -> &Song {
self.song.read()
}
pub fn finish(self) {}
}
#[derive(Debug, Clone, Copy)]
pub struct OutputConfig {
pub buffer_size: u32,
pub channel_count: NonZeroU16,
pub sample_rate: NonZero<u32>,
}
#[derive(Debug, Clone, Copy)]
pub enum PlaybackSettings {
Pattern { idx: u8, should_loop: bool },
Order { idx: u16, should_loop: bool },
}