torque_tracker_engine/audio_processing/
playback.rs

1use std::{num::NonZero, ops::ControlFlow};
2
3use crate::{
4    audio_processing::{sample::SamplePlayer, Frame},
5    channel::Pan,
6    manager::PlaybackSettings,
7    project::song::Song,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct PlaybackStatus {
12    pub position: PlaybackPosition,
13    // which sample is playing,
14    // which how far along is each sample
15    // which channel is playing
16    // ...
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct PlaybackPosition {
21    /// changes behaviour on pattern end and loop behaviour
22    pub order: Option<u16>,
23    pub pattern: u8,
24    pub row: u16,
25    /// if order is Some this loops the whole song, otherwise it loops the set pattern
26    pub loop_active: bool,
27}
28
29impl PlaybackPosition {
30    #[inline]
31    fn step_row(&mut self, song: &Song) -> ControlFlow<()> {
32        self.row += 1;
33        if self.row >= song.patterns[usize::from(self.pattern)].row_count() {
34            // reset row count
35            self.row = 0;
36            // compute next pattern
37            if let Some(order) = &mut self.order {
38                // next pattern according to song orderlist
39                if let Some(pattern) = song.next_pattern(order) {
40                    // song not finished yet
41                    self.pattern = pattern;
42                    ControlFlow::Continue(())
43                } else {
44                    // song is finished
45                    if !self.loop_active {
46                        // not looping, therefore break
47                        return ControlFlow::Break(());
48                    }
49                    // the song should loop
50                    // need to check if the song is empty now.
51                    *order = 0;
52                    if let Some(pattern) = song.next_pattern(order) {
53                        self.pattern = pattern;
54                        ControlFlow::Continue(())
55                    } else {
56                        // the song is empty, so playback is stopped
57                        ControlFlow::Break(())
58                    }
59                }
60            } else if self.loop_active {
61                // the row count was reset, nothing else to do
62                return ControlFlow::Continue(());
63            } else {
64                // no looping, pattern is done
65                return ControlFlow::Break(());
66            }
67        } else {
68            // Pattern not done yet
69            ControlFlow::Continue(())
70        }
71    }
72
73    /// if settings specify a pattern pattern always returns Some
74    fn new(settings: PlaybackSettings, song: &Song) -> Option<Self> {
75        match settings {
76            PlaybackSettings::Pattern { idx, should_loop } => {
77                // doesn't panic. patterns len is a constant
78                if idx < u8::try_from(song.patterns.len()).unwrap() {
79                    Some(Self {
80                        order: None,
81                        pattern: idx,
82                        row: 0,
83                        loop_active: should_loop,
84                    })
85                } else {
86                    None
87                }
88            }
89            PlaybackSettings::Order {
90                mut idx,
91                should_loop,
92            } => {
93                let pattern = song.next_pattern(&mut idx)?;
94                Some(Self {
95                    order: Some(idx),
96                    pattern,
97                    row: 0,
98                    loop_active: should_loop,
99                })
100            }
101        }
102    }
103}
104
105pub struct PlaybackState {
106    position: PlaybackPosition,
107    is_done: bool,
108    // both of these count down
109    tick: u8,
110    frame: u32,
111
112    // add current state to support Effects
113    samplerate: NonZero<u32>,
114
115    voices: [Option<SamplePlayer>; PlaybackState::VOICES],
116}
117
118impl PlaybackState {
119    // i don't know yet why those would be different. Splitting them up probably be a bit of work.
120    pub const VOICES: usize = Song::MAX_CHANNELS;
121
122    pub fn iter<'playback, 'song, const INTERPOLATION: u8>(
123        &'playback mut self,
124        song: &'song Song,
125    ) -> PlaybackIter<'song, 'playback, INTERPOLATION> {
126        PlaybackIter { state: self, song }
127    }
128
129    fn frames_per_tick(samplerate: NonZero<u32>, tempo: NonZero<u8>) -> u32 {
130        // don't ask me why times 2. it just does the same as schism now
131        (samplerate.get() * 2) / u32::from(tempo.get())
132    }
133
134    pub fn get_status(&self) -> PlaybackStatus {
135        // maybe if it gets more fields compute them while playing back and just copy out here
136        PlaybackStatus {
137            position: self.position,
138        }
139    }
140
141    pub fn set_samplerate(&mut self, samplerate: NonZero<u32>) {
142        self.samplerate = samplerate;
143        self.voices
144            .iter_mut()
145            .flatten()
146            .for_each(|voice| voice.set_out_samplerate(samplerate));
147    }
148
149    pub fn is_done(&self) -> bool {
150        self.is_done
151    }
152}
153
154impl PlaybackState {
155    /// None if the settings in the order variant don't have any pattern to play
156    pub fn new(song: &Song, samplerate: NonZero<u32>, settings: PlaybackSettings) -> Option<Self> {
157        let mut out = Self {
158            position: PlaybackPosition::new(settings, song)?,
159            is_done: false,
160            tick: song.initial_speed.get(),
161            frame: Self::frames_per_tick(samplerate, song.initial_tempo),
162            samplerate,
163            voices: std::array::from_fn(|_| None),
164        };
165        // Interpolation not important here. no interpolating is done. only sampledata is copied
166        out.iter::<0>(song).create_sample_players();
167        Some(out)
168    }
169}
170
171impl std::fmt::Debug for PlaybackState {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        f.debug_struct("PlaybackState")
174            .field("position", &self.position)
175            .field("tick", &self.tick)
176            .field("frame", &self.frame)
177            .field("samplerate", &self.samplerate)
178            .finish_non_exhaustive()?;
179        write!(
180            f,
181            "active channels: {}",
182            self.voices.iter().filter(|v| v.is_some()).count()
183        )
184    }
185}
186
187pub struct PlaybackIter<'song, 'playback, const INTERPOLATION: u8> {
188    state: &'playback mut PlaybackState,
189    song: &'song Song,
190}
191
192impl<const INTERPOLATION: u8> PlaybackIter<'_, '_, INTERPOLATION> {
193    pub fn frames_per_tick(&self) -> u32 {
194        PlaybackState::frames_per_tick(self.state.samplerate, self.song.initial_tempo)
195    }
196}
197
198impl<const INTERPOLATION: u8> Iterator for PlaybackIter<'_, '_, INTERPOLATION> {
199    type Item = Frame;
200
201    fn next(&mut self) -> Option<Self::Item> {
202        fn scale_vol(vol: u8) -> f32 {
203            (vol as f32) / (u8::MAX as f32)
204        }
205
206        /// scale from 0..=64 to 0°..=90° in radians
207        fn scale_pan(pan: u8) -> f32 {
208            debug_assert!((0..=64).contains(&pan));
209            (pan as f32) * const { (1. / 64.) * (std::f32::consts::FRAC_PI_2) }
210        }
211
212        if self.state.is_done {
213            return None;
214        }
215
216        debug_assert!(self.song.volume.len() == self.state.voices.len());
217        debug_assert!(self.song.pan.len() == self.state.voices.len());
218
219        let out: Frame = self
220            .state
221            .voices
222            .iter_mut()
223            .zip(self.song.volume)
224            .zip(self.song.pan)
225            .flat_map(|((channel, vol), pan)| {
226                if let Some(voice) = channel {
227                    let mut out = voice.next::<INTERPOLATION>().unwrap();
228                    // this logic removes the voices as soon as possible
229                    if voice.check_position().is_break() {
230                        *channel = None;
231                    }
232                    // add volume and panning
233                    let channel_vol = scale_vol(vol);
234                    if let Pan::Value(pan) = pan {
235                        let angle = scale_pan(pan);
236                        out.pan_constant_power(angle);
237                    }
238                    Some(out * channel_vol)
239                    // this logic frees the voices one frame later than possible
240                    // match voice.next::<INTERPOLATION>() {
241                    //     Some(frame) => Some(frame),
242                    //     None => {
243                    //         *channel = None;
244                    //         None
245                    //     }
246                    // }
247                } else {
248                    None
249                }
250            })
251            .sum();
252        self.step();
253        let out_vol = scale_vol(self.song.global_volume);
254        Some(out * out_vol)
255    }
256}
257
258impl<const INTERPOLATION: u8> PlaybackIter<'_, '_, INTERPOLATION> {
259    fn step(&mut self) {
260        // the current speed is a bit off from schism tracker. i don't know why, how much or in which direction.
261        if self.state.frame > 0 {
262            self.state.frame -= 1;
263            return;
264        } else {
265            self.state.frame = self.frames_per_tick();
266        }
267
268        if self.state.tick > 0 {
269            self.state.tick -= 1;
270            return;
271        } else {
272            self.state.tick = self.song.initial_speed.get();
273        }
274
275        match self.state.position.step_row(self.song) {
276            ControlFlow::Continue(_) => self.create_sample_players(),
277            ControlFlow::Break(_) => self.state.is_done = true,
278        }
279    }
280    fn create_sample_players(&mut self) {
281        for (position, event) in
282            &self.song.patterns[usize::from(self.state.position.pattern)][self.state.position.row]
283        {
284            if let Some((meta, ref sample)) = self.song.samples[usize::from(event.sample_instr)] {
285                let player =
286                    SamplePlayer::new(sample.clone(), meta, self.state.samplerate, event.note);
287                self.state.voices[usize::from(position.channel)] = Some(player);
288            }
289        }
290    }
291}