old school music tracker audio backend
at main 291 lines 9.9 kB view raw
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}