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