old school music tracker audio backend

Pattern Operation + small stuff

luca3s cbefe27f 52561c76

+144 -131
Documentation/AudioInterpolationTypes.pdf

This is a binary file and will not be displayed.

+13 -9
examples/pattern_playback.rs
··· 8 8 song::{ 9 9 event_command::NoteCommand, 10 10 note_event::{Note, NoteEvent, VolumeEffect}, 11 - pattern::InPatternPosition, 11 + pattern::{InPatternPosition, PatternOperation}, 12 12 song::Song, 13 13 }, 14 14 }; ··· 33 33 let mut song = manager.edit_song(); 34 34 song.set_sample(0, meta, sample); 35 35 for i in 0..12 { 36 - song.set_note_event( 37 - 0, 38 - InPatternPosition { row: i, channel: i as u8 }, 39 - NoteEvent { 36 + let command = PatternOperation::SetEvent { 37 + position: InPatternPosition { 38 + row: i, 39 + channel: i as u8, 40 + }, 41 + event: NoteEvent { 40 42 note: Note::new(60 + i as u8).unwrap(), 41 43 sample_instr: 0, 42 44 vol: VolumeEffect::None, 43 45 command: NoteCommand::None, 44 46 }, 45 - ); 47 + }; 48 + song.pattern_operation(0, command); 46 49 } 47 50 song.set_order( 48 51 0, ··· 77 80 manager.play_song(PlaybackSettings {}); 78 81 79 82 std::thread::sleep(Duration::from_secs(5)); 80 - while let Ok(event) = recv.try_next() { 81 - println!("{event:?}"); 82 - } 83 + manager.deinit_audio(); 84 + // while let Ok(event) = recv.try_next() { 85 + // println!("{event:?}"); 86 + // } 83 87 }
-1
simple-left-right/src/inner.rs
··· 1 - 2 1 use core::{cell::UnsafeCell, hint::unreachable_unchecked, sync::atomic::AtomicU8}; 3 2 4 3 #[derive(PartialEq, Eq, Clone, Copy, Debug)]
+25 -7
simple-left-right/src/lib.rs
··· 1 - #![warn(clippy::all, clippy::pedantic, clippy::perf, clippy::style, clippy::complexity, clippy::suspicious, clippy::correctness)] 2 - 3 - use std::{borrow::Borrow, cell::UnsafeCell, collections::VecDeque, marker::PhantomData, ops::Deref, sync::{atomic::{fence, AtomicU8, Ordering}, Arc}}; 1 + #![warn( 2 + clippy::all, 3 + clippy::pedantic, 4 + clippy::perf, 5 + clippy::style, 6 + clippy::complexity, 7 + clippy::suspicious, 8 + clippy::correctness 9 + )] 4 10 5 - use inner::{Ptr, ReadState, Shared}; 11 + use std::{ 12 + borrow::Borrow, 13 + cell::UnsafeCell, 14 + collections::VecDeque, 15 + marker::PhantomData, 16 + ops::Deref, 17 + sync::{ 18 + atomic::{fence, AtomicU8, Ordering}, 19 + Arc, 20 + }, 21 + }; 6 22 7 23 mod inner; 24 + 25 + use inner::{Ptr, ReadState, Shared}; 8 26 9 27 pub trait Absorb<O> { 10 28 /// has to be deterministic. Operations will be applied in the same order to both buffers ··· 129 147 inner.value_1.get_mut().absorb(operation.clone()); 130 148 inner.value_2.get_mut().absorb(operation); 131 149 } else { 132 - self.get_data_mut().absorb(operation.clone()); 133 - self.writer.op_buffer.push_back(operation); 150 + self.writer.op_buffer.push_back(operation.clone()); 151 + self.get_data_mut().absorb(operation); 134 152 } 135 153 } 136 154 } ··· 313 331 } 314 332 } 315 333 316 - /// This impl is only ok because this is an internal library. 334 + /// This impl is only ok because this is an internal library. 317 335 /// If one would publish the library it would lead to function name collisions 318 336 /// If there ever is a internal collision, rename a function or remove this impl 319 337 impl<T, O> Deref for Writer<T, O> {
+29 -8
src/audio_processing/sample.rs
··· 1 1 use std::ops::{ControlFlow, Deref}; 2 2 3 - use crate::{sample::{SampleData, SampleMetaData, SampleRef}, song::note_event::{Note, NoteEvent}}; 3 + use crate::{ 4 + sample::{SampleData, SampleMetaData, SampleRef}, 5 + song::note_event::Note, 6 + }; 4 7 5 8 use super::Frame; 6 9 ··· 61 64 meta_data: sample.0, 62 65 position: (SampleData::PAD_SIZE_EACH, 0.), 63 66 out_rate, 64 - step_size: Self::compute_step_size(sample.0.sample_rate, out_rate, sample.0.base_note, note), 67 + step_size: Self::compute_step_size( 68 + sample.0.sample_rate, 69 + out_rate, 70 + sample.0.base_note, 71 + note, 72 + ), 65 73 note, 66 74 } 67 75 } ··· 74 82 } 75 83 } 76 84 77 - fn compute_step_size(in_rate: u32, out_rate: u32, sample_base_note: Note, playing_note: Note) -> f32 { 78 - (f32::from(i16::from(playing_note.get()) - i16::from(sample_base_note.get())) / 12. ).exp2() * (out_rate as f32 / in_rate as f32) 85 + fn compute_step_size( 86 + in_rate: u32, 87 + out_rate: u32, 88 + sample_base_note: Note, 89 + playing_note: Note, 90 + ) -> f32 { 91 + // original formula: (outrate / inrate) * (playing_freq / sample_base_freq). 92 + // Where each freq is computed with MIDI tuning standard formula: 440 * 2^((note - 69)/12) 93 + // manually reduced formula: 2^((play_note - sample_base_note)/12) * (outrate / inrate) 94 + // herbie (https://herbie.uwplse.org/demo/index.html) can't optimize further: https://herbie.uwplse.org/demo/e096ef89ee257ad611dd56378bd139a065a6bea0.02e7ec5a3709ad3e06968daa97db50d636f1e44b/graph.html 95 + (f32::from(i16::from(playing_note.get()) - i16::from(sample_base_note.get())) / 12.).exp2() * (out_rate as f32 / in_rate as f32) 79 96 } 80 97 81 98 fn set_step_size(&mut self) { 82 - self.step_size = Self::compute_step_size(self.meta_data.sample_rate, self.out_rate, self.meta_data.base_note, self.note); 99 + self.step_size = Self::compute_step_size( 100 + self.meta_data.sample_rate, 101 + self.out_rate, 102 + self.meta_data.base_note, 103 + self.note, 104 + ); 83 105 } 84 106 85 107 pub fn set_out_samplerate(&mut self, samplerate: u32) { ··· 120 142 Frame::from((diff * self.position.1) + mono[self.position.0]) 121 143 } 122 144 SampleData::Stereo(stereo) => { 123 - let diff: Frame = 124 - Frame::from(stereo[self.position.0 + 1]) - Frame::from(stereo[self.position.0]); 145 + let diff = Frame::from(stereo[self.position.0 + 1]) - Frame::from(stereo[self.position.0]); 125 146 126 - (diff * self.position.1) + stereo[self.position.0].into() 147 + (diff * self.position.1) + Frame::from(stereo[self.position.0]) 127 148 } 128 149 }; 129 150
+26 -39
src/live_audio.rs
··· 10 10 use crate::song::song::Song; 11 11 use crate::{audio_processing::sample::SamplePlayer, playback::PlaybackState}; 12 12 use cpal::{Sample, SampleFormat}; 13 + use simple_left_right::Reader; 13 14 14 15 pub(crate) struct LiveAudio { 15 - song: simple_left_right::Reader<Song<true>>, 16 + song: Reader<Song<true>>, 16 17 playback_state: Option<PlaybackState<'static, true>>, 17 18 live_note: Option<SamplePlayer<'static, true>>, 18 19 manager: Receiver<ToWorkerMsg>, ··· 26 27 const INTERPOLATION: u8 = Interpolation::Linear as u8; 27 28 28 29 pub fn new( 29 - song: simple_left_right::Reader<Song<true>>, 30 + song: Reader<Song<true>>, 30 31 manager: Receiver<ToWorkerMsg>, 31 32 audio_msg_config: AudioMsgConfig, 32 33 to_app: futures::channel::mpsc::Sender<FromWorkerMsg>, ··· 45 46 } 46 47 47 48 #[inline] 48 - /// returns if work was done 49 + /// returns true if work was done 49 50 fn fill_internal_buffer(&mut self) -> bool { 50 51 let song = self.song.lock(); 51 52 ··· 134 135 * usize::from(self.config.channel_count.get()) 135 136 ); 136 137 137 - // actual audio work 138 - if self.fill_internal_buffer() { 139 - // convert to the right output format 140 - match data.sample_format() { 141 - SampleFormat::I8 => self.fill_from_internal::<i8>(data.as_slice_mut().unwrap()), 142 - SampleFormat::I16 => { 143 - self.fill_from_internal::<i16>(data.as_slice_mut().unwrap()) 144 - } 145 - SampleFormat::I32 => { 146 - self.fill_from_internal::<i32>(data.as_slice_mut().unwrap()) 147 - } 148 - SampleFormat::I64 => { 149 - self.fill_from_internal::<i64>(data.as_slice_mut().unwrap()) 150 - } 151 - SampleFormat::U8 => self.fill_from_internal::<u8>(data.as_slice_mut().unwrap()), 152 - SampleFormat::U16 => { 153 - self.fill_from_internal::<u16>(data.as_slice_mut().unwrap()) 154 - } 155 - SampleFormat::U32 => { 156 - self.fill_from_internal::<u32>(data.as_slice_mut().unwrap()) 157 - } 158 - SampleFormat::U64 => { 159 - self.fill_from_internal::<u64>(data.as_slice_mut().unwrap()) 160 - } 161 - SampleFormat::F32 => { 162 - self.fill_from_internal::<f32>(data.as_slice_mut().unwrap()) 163 - } 164 - SampleFormat::F64 => { 165 - self.fill_from_internal::<f64>(data.as_slice_mut().unwrap()) 166 - } 167 - /* 168 - I want to support all formats. This panic being triggered means that there is a version 169 - mismatch between cpal and this library. 170 - */ 171 - _ => panic!("Sample Format not supported."), 172 - } 138 + // actual audio work, if false no work was done 139 + if !self.fill_internal_buffer() { 140 + return; 141 + } 142 + 143 + // convert to the right output format 144 + match data.sample_format() { 145 + SampleFormat::I8 => self.fill_from_internal::<i8>(data.as_slice_mut().unwrap()), 146 + SampleFormat::I16 => self.fill_from_internal::<i16>(data.as_slice_mut().unwrap()), 147 + SampleFormat::I32 => self.fill_from_internal::<i32>(data.as_slice_mut().unwrap()), 148 + SampleFormat::I64 => self.fill_from_internal::<i64>(data.as_slice_mut().unwrap()), 149 + SampleFormat::U8 => self.fill_from_internal::<u8>(data.as_slice_mut().unwrap()), 150 + SampleFormat::U16 => self.fill_from_internal::<u16>(data.as_slice_mut().unwrap()), 151 + SampleFormat::U32 => self.fill_from_internal::<u32>(data.as_slice_mut().unwrap()), 152 + SampleFormat::U64 => self.fill_from_internal::<u64>(data.as_slice_mut().unwrap()), 153 + SampleFormat::F32 => self.fill_from_internal::<f32>(data.as_slice_mut().unwrap()), 154 + SampleFormat::F64 => self.fill_from_internal::<f64>(data.as_slice_mut().unwrap()), 155 + /* 156 + I want to support all formats. This panic being triggered means that there is a version 157 + mismatch between cpal and this library. 158 + */ 159 + _ => panic!("Sample Format not supported."), 173 160 } 174 161 175 162 if self.audio_msg_config.buffer_finished {
+12 -29
src/manager/audio_manager.rs
··· 1 - use std::num::NonZeroU16; 1 + use std::{mem::ManuallyDrop, num::NonZeroU16}; 2 2 3 3 use basedrop::{Collector, Handle, Shared}; 4 4 use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; 5 + use simple_left_right::{WriteGuard, Writer}; 5 6 6 7 use crate::{ 7 8 channel::Pan, ··· 10 11 sample::{SampleData, SampleMetaData}, 11 12 song::{ 12 13 note_event::NoteEvent, 13 - pattern::InPatternPosition, 14 + pattern::{InPatternPosition, Pattern, PatternOperation}, 14 15 song::{Song, SongOperation}, 15 16 }, 16 17 }; 17 18 18 19 pub struct AudioManager { 19 - song: simple_left_right::Writer<Song<true>, SongOperation>, 20 - gc: std::mem::ManuallyDrop<Collector>, 20 + song: Writer<Song<true>, SongOperation>, 21 + gc: ManuallyDrop<Collector>, 21 22 stream: Option<(cpal::Stream, std::sync::mpsc::Sender<ToWorkerMsg>)>, 22 23 } 23 24 ··· 99 100 } 100 101 } 101 102 102 - /// resume the audio thread. doesn't start any playback 103 + /// resume the audio thread. doesn't start any playback (only on some platforms, see cpal docs for stream.pause()) 103 104 pub fn resume_audio(&self) { 104 105 if let Some((stream, _)) = &self.stream { 105 106 stream.play().unwrap(); 106 107 } 107 108 } 108 109 109 - /// need to think about the API more 110 110 pub fn play_note(&self, note_event: crate::song::note_event::NoteEvent) { 111 111 if let Some((_, channel)) = &self.stream { 112 112 channel.send(ToWorkerMsg::PlayEvent(note_event)).unwrap(); ··· 150 150 // should do all the verfication of 151 151 // need manuallyDrop because i need consume on drop behaviour 152 152 pub struct SongEdit<'a> { 153 - song: std::mem::ManuallyDrop< 154 - simple_left_right::WriteGuard<'a, Song<true>, SongOperation>, 155 - >, 153 + song: ManuallyDrop<WriteGuard<'a, Song<true>, SongOperation>>, 156 154 gc_handle: Handle, 157 155 } 158 156 ··· 175 173 self.song.apply_op(op); 176 174 } 177 175 178 - pub fn set_note_event( 179 - &mut self, 180 - pattern: usize, 181 - position: InPatternPosition, 182 - event: NoteEvent, 183 - ) { 176 + pub fn pattern_operation(&mut self, pattern: usize, op: PatternOperation) { 184 177 assert!(pattern < Song::<false>::MAX_PATTERNS); 185 - assert!(position.row < self.song.patterns[pattern].row_count()); 186 - let op = SongOperation::SetNoteEvent(pattern, position, event); 187 - self.song.apply_op(op); 188 - } 189 - 190 - pub fn remove_note_event(&mut self, pattern: usize, position: InPatternPosition) { 191 - assert!(pattern < Song::<false>::MAX_PATTERNS); 192 - assert!(position.row < self.song.patterns[pattern].row_count()); 193 - let op = SongOperation::DeleteNoteEvent(pattern, position); 194 - self.song.apply_op(op); 178 + assert!(self.song.patterns[pattern].operation_is_valid(&op)); 179 + self.song.apply_op(SongOperation::PatternOperation(pattern, op)); 195 180 } 196 181 197 182 pub fn set_order(&mut self, idx: usize, order: PatternOrder) { ··· 205 190 } 206 191 207 192 /// Finish the changes and publish them to the live playing song 208 - pub fn finish(self) { 209 - drop(self) 210 - } 193 + pub fn finish(self) {} 211 194 } 212 195 213 196 impl Drop for SongEdit<'_> { ··· 215 198 // SAFETY: 216 199 // the ManuallyDrop isn't used after this as this is the drop function 217 200 // can't use into_inner, as i only have &mut not owned 218 - unsafe { std::mem::ManuallyDrop::take(&mut self.song) }.swap() 201 + unsafe { ManuallyDrop::take(&mut self.song) }.swap() 219 202 } 220 203 } 221 204
+5
src/playback.rs
··· 43 43 pub fn get_position(&self) -> PlaybackPosition { 44 44 self.position 45 45 } 46 + 47 + pub fn set_samplerate(&mut self, samplerate: u32) { 48 + self.samplerate = samplerate; 49 + self.voices.iter_mut().flatten().for_each(|voice| voice.set_out_samplerate(samplerate)); 50 + } 46 51 } 47 52 48 53 macro_rules! new {
+30 -26
src/song/pattern.rs
··· 12 12 pub channel: u8, 13 13 } 14 14 15 - const fn key(data: &(InPatternPosition, NoteEvent)) -> InPatternPosition { 16 - data.0 17 - } 18 - 19 15 #[cfg(test)] 20 16 mod test { 21 17 use crate::song::pattern::InPatternPosition; ··· 34 30 data: Vec<(InPatternPosition, NoteEvent)>, 35 31 } 36 32 33 + const fn key(data: &(InPatternPosition, NoteEvent)) -> InPatternPosition { 34 + data.0 35 + } 36 + 37 37 impl Default for Pattern { 38 38 fn default() -> Self { 39 39 Self::new(Self::DEFAULT_ROWS) ··· 46 46 pub const DEFAULT_ROWS: u16 = 64; 47 47 48 48 /// panics if len larger than 'Self::MAX_LEN' 49 - pub fn new(len: u16) -> Self { 49 + pub const fn new(len: u16) -> Self { 50 50 assert!(len <= Self::MAX_ROWS); 51 51 Self { 52 52 rows: len, ··· 60 60 assert!(new_len <= Self::MAX_ROWS); 61 61 // gets the index of the first element of the first row to be removed 62 62 if new_len < self.rows { 63 - let idx = self 64 - .data 65 - .binary_search_by_key( 66 - &InPatternPosition { 67 - row: new_len, 68 - channel: 0, 69 - }, 70 - |(pos, _)| *pos, 71 - ) 72 - .unwrap_or_else(|i| i); 63 + let idx = self.data.partition_point(|(pos, _)| pos.row < new_len); 73 64 self.data.truncate(idx); 74 65 } 75 66 self.rows = new_len; ··· 78 69 /// overwrites the event if the row already has an event for that channel 79 70 /// panics if the row position is larger than current amount of rows 80 71 pub fn set_event(&mut self, position: InPatternPosition, event: NoteEvent) { 81 - assert!(self.rows > position.row); 72 + assert!(position.row < self.rows); 82 73 match self.data.binary_search_by_key(&position, key) { 83 74 Ok(idx) => self.data[idx].1 = event, 84 75 Err(idx) => self.data.insert(idx, (position, event)), ··· 101 92 102 93 /// if there is no event, does nothing 103 94 pub fn remove_event(&mut self, position: InPatternPosition) { 104 - if let Ok(index) = self.data.binary_search_by_key(&position, |(pos, _)| *pos) { 95 + if let Ok(index) = self.data.binary_search_by_key(&position, key) { 105 96 self.data.remove(index); 106 97 } 107 98 } 108 99 109 - pub fn row_count(&self) -> u16 { 100 + pub const fn row_count(&self) -> u16 { 110 101 self.rows 111 102 } 103 + 104 + /// Panics if the Operation is invalid 105 + pub fn apply_operation(&mut self, op: PatternOperation) { 106 + match op { 107 + PatternOperation::SetLength { new_len } => self.set_length(new_len), 108 + PatternOperation::SetEvent { position, event } => self.set_event(position, event), 109 + PatternOperation::RemoveEvent { position } => self.remove_event(position), 110 + 111 + } 112 + } 113 + 114 + pub const fn operation_is_valid(&self, op: &PatternOperation) -> bool { 115 + match op { 116 + PatternOperation::SetLength { new_len } => *new_len < Self::MAX_ROWS, 117 + PatternOperation::SetEvent { position, event: _ } => position.row < self.rows && position.channel as usize <= Song::<false>::MAX_CHANNELS, 118 + PatternOperation::RemoveEvent { position: _ } => true, 119 + } 120 + } 112 121 } 113 122 114 123 impl Index<u16> for Pattern { ··· 148 157 } 149 158 150 159 /// assumes the Operations are correct (not out of bounds, ...) 160 + #[derive(Debug, Clone, Copy)] 151 161 pub enum PatternOperation { 152 - Load(Box<[Pattern; Song::<false>::MAX_PATTERNS]>), 153 162 SetLength { 154 - pattern: usize, 155 163 new_len: u16, 156 164 }, 157 165 SetEvent { 158 - pattern: usize, 159 - row: u16, 160 - channel: u8, 166 + position: InPatternPosition, 161 167 event: NoteEvent, 162 168 }, 163 169 RemoveEvent { 164 - pattern: usize, 165 - row: u16, 166 - channel: u8, 170 + position: InPatternPosition, 167 171 }, 168 172 }
+4 -12
src/song/song.rs
··· 21 21 use basedrop::Shared; 22 22 23 23 use super::note_event::NoteEvent; 24 - use super::pattern::InPatternPosition; 24 + use super::pattern::{InPatternPosition, PatternOperation}; 25 25 26 26 #[derive(Clone)] 27 27 pub struct Project<const GC: bool> { ··· 147 147 SetVolume(usize, u8), 148 148 SetPan(usize, Pan), 149 149 SetSample(usize, SampleMetaData, Shared<SampleData>), 150 - SetNoteEvent(usize, InPatternPosition, NoteEvent), 151 - DeleteNoteEvent(usize, InPatternPosition), 150 + PatternOperation(usize, PatternOperation), 152 151 SetOrder(usize, PatternOrder), 153 152 } 154 153 ··· 157 156 match operation { 158 157 SongOperation::SetVolume(chan, val) => self.volume[chan] = val, 159 158 SongOperation::SetPan(chan, val) => self.pan[chan] = val, 160 - SongOperation::SetSample(sample, meta, data) => { 161 - self.samples[sample] = Some((meta, Sample::<true>::new(data))) 162 - } 163 - SongOperation::SetNoteEvent(pattern, position, event) => { 164 - self.patterns[pattern].set_event(position, event) 165 - } 166 - SongOperation::DeleteNoteEvent(pattern, position) => { 167 - self.patterns[pattern].remove_event(position) 168 - } 159 + SongOperation::SetSample(sample, meta, data) => self.samples[sample] = Some((meta, Sample::<true>::new(data))), 160 + SongOperation::PatternOperation(pattern, op) => self.patterns[pattern].apply_operation(op), 169 161 SongOperation::SetOrder(idx, order) => self.pattern_order[idx] = order, 170 162 } 171 163 }