old school music tracker audio backend
at dev 297 lines 10 kB view raw
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}