old school music tracker audio backend
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}