torque_tracker_engine/
live_audio.rs

1use std::ops::{AddAssign, IndexMut};
2
3use crate::audio_processing::playback::{PlaybackState, PlaybackStatus};
4use crate::audio_processing::sample::Interpolation;
5use crate::audio_processing::sample::SamplePlayer;
6use crate::audio_processing::Frame;
7use crate::manager::{OutputConfig, ToWorkerMsg};
8use crate::project::song::Song;
9use crate::sample::Sample;
10use dasp::sample::ToSample;
11use simple_left_right::Reader;
12
13pub(crate) struct LiveAudio {
14    song: Reader<Song>,
15    playback_state: Option<PlaybackState>,
16    live_note: Option<SamplePlayer>,
17    manager: rtrb::Consumer<ToWorkerMsg>,
18    state_sender: triple_buffer::Input<Option<PlaybackStatus>>,
19    config: OutputConfig,
20
21    buffer: Box<[Frame]>,
22}
23
24// should probabyl be made configurable at some point
25const INTERPOLATION: u8 = Interpolation::Linear as u8;
26
27impl LiveAudio {
28    /// Not realtime safe.
29    pub fn new(
30        song: Reader<Song>,
31        manager: rtrb::Consumer<ToWorkerMsg>,
32        state_sender: triple_buffer::Input<Option<PlaybackStatus>>,
33        config: OutputConfig,
34    ) -> Self {
35        Self {
36            song,
37            playback_state: None,
38            live_note: None,
39            manager,
40            state_sender,
41            config,
42            buffer: vec![Frame::default(); usize::try_from(config.buffer_size).unwrap() * 2].into(),
43        }
44    }
45
46    #[rtsan_standalone::nonblocking]
47    fn send_state(&mut self) {
48        self.state_sender
49            .write(self.playback_state.as_ref().map(|s| s.get_status()));
50    }
51
52    #[rtsan_standalone::nonblocking]
53    /// returns true if work was done
54    fn fill_internal_buffer(&mut self, len: usize) -> bool {
55        // the output buffer should be smaller than the internal buffer
56        let buffer = &mut self.buffer[..len];
57
58        let song = self.song.lock();
59
60        // process manager events
61        while let Ok(event) = self.manager.pop() {
62            match event {
63                ToWorkerMsg::StopPlayback => self.playback_state = None,
64                ToWorkerMsg::Playback(settings) => {
65                    self.playback_state =
66                        PlaybackState::new(&song, self.config.sample_rate, settings);
67                }
68                ToWorkerMsg::PlayEvent(note) => {
69                    if let Some(sample) = &song.samples[usize::from(note.sample_instr)] {
70                        let sample_player = SamplePlayer::new(
71                            Sample::clone(&sample.1),
72                            sample.0,
73                            // this at some point was divided by two, if i ever figure out why, maybe put it back
74                            self.config.sample_rate,
75                            note.note,
76                        );
77                        self.live_note = Some(sample_player);
78                    }
79                }
80                ToWorkerMsg::StopLiveNote => self.live_note = None,
81            }
82        }
83        if self.live_note.is_none() && self.playback_state.is_none() {
84            // no processing todo
85            return false;
86        }
87
88        // clear buffer from past run
89        // only happens if there is work todo
90        buffer.fill(Frame::default());
91
92        // process live_note
93        if let Some(live_note) = &mut self.live_note {
94            let note_iter = live_note.iter::<{ INTERPOLATION }>();
95            buffer
96                .iter_mut()
97                .zip(note_iter)
98                .for_each(|(buf, note)| buf.add_assign(note));
99
100            if live_note.check_position().is_break() {
101                self.live_note = None;
102            }
103        }
104
105        // process song playback
106        if let Some(playback) = &mut self.playback_state {
107            let playback_iter = playback.iter::<{ INTERPOLATION }>(&song);
108            buffer
109                .iter_mut()
110                .zip(playback_iter)
111                .for_each(|(buf, frame)| buf.add_assign(frame));
112
113            if playback.is_done() {
114                self.playback_state = None;
115            }
116        }
117
118        true
119    }
120
121    /// converts the internal buffer to any possible output format and channel count
122    /// sums stereo to mono and fills channels 3 and up with silence
123    #[rtsan_standalone::nonblocking]
124    #[inline]
125    fn fill_from_internal<Sample: dasp::sample::Sample + dasp::sample::FromSample<f32>>(
126        &mut self,
127        data: &mut [Sample],
128    ) {
129        // convert the internal buffer and move it to the out_buffer
130        if self.config.channel_count.get() == 1 {
131            data.iter_mut()
132                .zip(self.buffer.iter())
133                .for_each(|(out, buf)| *out = buf.sum_to_mono().to_sample_());
134        } else {
135            data.chunks_exact_mut(usize::from(self.config.channel_count.get()))
136                .map(|frame| frame.split_first_chunk_mut::<2>().unwrap().0)
137                .zip(self.buffer.iter())
138                .for_each(|(out, buf)| *out = buf.to_sample());
139        }
140    }
141
142    // unsure wether i want to use this or untyped_callback
143    // also relevant when cpal gets made into a generic that maybe this gets useful
144    pub fn get_typed_callback<Sample: dasp::sample::Sample + dasp::sample::FromSample<f32>>(
145        mut self,
146    ) -> impl FnMut(&mut [Sample]) {
147        move |data| {
148            let channel_count = usize::from(self.config.channel_count.get());
149            assert!(data.len().is_multiple_of(channel_count));
150            let out_frames = data.len() / channel_count;
151            assert!(self.buffer.len() > out_frames);
152            // assert_eq!(
153            //     data.len(),
154            //     usize::try_from(self.config.buffer_size).unwrap()
155            //         * usize::from(self.config.channel_count.get())
156            // );
157
158            if self.fill_internal_buffer(out_frames) {
159                self.fill_from_internal(data);
160            }
161            self.send_state();
162        }
163        // move |data, info| {
164        //     assert_eq!(
165        //         data.len(),
166        //         usize::try_from(self.config.buffer_size).unwrap()
167        //             * usize::from(self.config.channel_count.get())
168        //     );
169        // self.send_state(Some(info));
170        // }
171    }
172
173    // pub fn get_callback(mut self) -> impl FnMut(&mut [Frame], S::BufferInformation) {
174    //     move |data, info| {
175    //         assert_eq!(data.len(), self.config.buffer_size as usize * self.config.channel_count.get() as usize)
176
177    //         if self.fill_internal_buffer() {
178    //             self.fill_from_internal(data);
179    //         }
180    //     }
181    // }
182}
183
184// only used for testing
185// if not testing is unused
186#[allow(dead_code)]
187fn sine(output: &mut [[f32; 2]], sample_rate: f32) {
188    let mut sample_clock = 0f32;
189    for frame in output {
190        sample_clock = (sample_clock + 1.) % sample_rate;
191        let value = (sample_clock * 440. * 2. * std::f32::consts::PI / sample_rate).sin();
192        *frame.index_mut(0) = value;
193        *frame.index_mut(1) = value;
194    }
195}