From bbd60cdaae605cbfd7b3c20ef6581a9fb7540cd9 Mon Sep 17 00:00:00 2001 From: Lucas Baumann Date: Sun, 13 Jul 2025 21:15:18 +0200 Subject: [PATCH] small cleanups & bugfix --- Cargo.lock | 2 +- examples/live_note.rs | 20 +++++++++++++++----- examples/pattern_playback.rs | 27 +++++++++++++++++---------- src/audio_processing/playback.rs | 12 ++++++------ src/audio_processing/sample.rs | 18 ++++++++++-------- src/live_audio.rs | 4 +++- src/manager.rs | 10 +++++++--- src/sample.rs | 8 ++++++-- 8 files changed, 65 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9be672..dbd3efc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -707,7 +707,7 @@ dependencies = [ ] [[package]] -name = "torque_tracker_engine" +name = "torque-tracker-engine" version = "0.1.0" dependencies = [ "cpal", diff --git a/examples/live_note.rs b/examples/live_note.rs index ecbf10f..844560e 100644 --- a/examples/live_note.rs +++ b/examples/live_note.rs @@ -1,7 +1,11 @@ -use std::{num::NonZeroU16, time::Duration}; +use std::{ + num::{NonZero, NonZeroU16}, + time::Duration, +}; use cpal::traits::{DeviceTrait, HostTrait}; use torque_tracker_engine::{ + file::impulse_format::sample::VibratoWave, manager::{AudioManager, OutputConfig, ToWorkerMsg}, project::{ event_command::NoteCommand, @@ -22,9 +26,15 @@ fn main() { .map(|result| ::from_sample(result.unwrap())); let sample = Sample::new_mono(sample_data); let meta = SampleMetaData { - sample_rate: spec.sample_rate, + sample_rate: NonZero::new(spec.sample_rate).unwrap(), default_volume: 150, - ..Default::default() + global_volume: 20, + default_pan: None, + vibrato_speed: 0, + vibrato_depth: 0, + vibrato_rate: 0, + vibrato_waveform: VibratoWave::default(), + base_note: Note::new(64).unwrap(), }; manager @@ -41,7 +51,7 @@ fn main() { let config = OutputConfig { buffer_size: 2048, channel_count: NonZeroU16::new(2).unwrap(), - sample_rate: default_config.sample_rate().0, + sample_rate: NonZero::new(default_config.sample_rate().0).unwrap(), }; let mut audio_callback = manager.get_callback::(config); @@ -55,7 +65,7 @@ fn main() { .unwrap(); let note_event = NoteEvent { - note: Note::new(90).unwrap(), + note: Note::new(70).unwrap(), sample_instr: 1, vol: VolumeEffect::None, command: NoteCommand::None, diff --git a/examples/pattern_playback.rs b/examples/pattern_playback.rs index e351143..c58057a 100644 --- a/examples/pattern_playback.rs +++ b/examples/pattern_playback.rs @@ -1,7 +1,11 @@ -use std::{num::NonZeroU16, time::Duration}; +use std::{ + num::{NonZero, NonZeroU16}, + time::Duration, +}; use cpal::traits::{DeviceTrait, HostTrait}; -use tracker_engine::{ +use torque_tracker_engine::{ + file::impulse_format::{header::PatternOrder, sample::VibratoWave}, manager::{AudioManager, OutputConfig, PlaybackSettings, ToWorkerMsg}, project::{ event_command::NoteCommand, @@ -23,9 +27,15 @@ fn main() { .map(|result| ::from_sample(result.unwrap())); let sample = Sample::new_mono(sample_data); let meta = SampleMetaData { - sample_rate: spec.sample_rate, + sample_rate: NonZero::new(spec.sample_rate).unwrap(), base_note: Note::new(64).unwrap(), - ..Default::default() + default_volume: 20, + global_volume: 20, + default_pan: None, + vibrato_speed: 0, + vibrato_depth: 0, + vibrato_rate: 0, + vibrato_waveform: VibratoWave::default(), }; let mut song = manager.try_edit_song().unwrap(); @@ -47,11 +57,8 @@ fn main() { song.apply_operation(SongOperation::PatternOperation(0, command)) .unwrap(); } - song.apply_operation(SongOperation::SetOrder( - 0, - tracker_engine::file::impulse_format::header::PatternOrder::Number(0), - )) - .unwrap(); + song.apply_operation(SongOperation::SetOrder(0, PatternOrder::Number(0))) + .unwrap(); song.finish(); @@ -63,7 +70,7 @@ fn main() { let config = OutputConfig { buffer_size: 1024, channel_count: NonZeroU16::new(2).unwrap(), - sample_rate: default_config.sample_rate().0, + sample_rate: NonZero::new(default_config.sample_rate().0).unwrap(), }; let mut callback = manager.get_callback::(config); diff --git a/src/audio_processing/playback.rs b/src/audio_processing/playback.rs index e50d074..045a1d3 100644 --- a/src/audio_processing/playback.rs +++ b/src/audio_processing/playback.rs @@ -1,4 +1,4 @@ -use std::ops::ControlFlow; +use std::{num::NonZero, ops::ControlFlow}; use crate::{ audio_processing::{sample::SamplePlayer, Frame}, @@ -108,7 +108,7 @@ pub struct PlaybackState { frame: u32, // add current state to support Effects - samplerate: u32, + samplerate: NonZero, voices: [Option; PlaybackState::VOICES], } @@ -123,8 +123,8 @@ impl PlaybackState { PlaybackIter { state: self, song } } - fn frames_per_tick(samplerate: u32, tempo: u8) -> u32 { - (samplerate * 10) / u32::from(tempo) + fn frames_per_tick(samplerate: NonZero, tempo: u8) -> u32 { + (samplerate.get() * 10) / u32::from(tempo) } pub fn get_status(&self) -> PlaybackStatus { @@ -134,7 +134,7 @@ impl PlaybackState { } } - pub fn set_samplerate(&mut self, samplerate: u32) { + pub fn set_samplerate(&mut self, samplerate: NonZero) { self.samplerate = samplerate; self.voices .iter_mut() @@ -149,7 +149,7 @@ impl PlaybackState { impl PlaybackState { /// None if the settings in the order variant don't have any pattern to play - pub fn new(song: &Song, samplerate: u32, settings: PlaybackSettings) -> Option { + pub fn new(song: &Song, samplerate: NonZero, settings: PlaybackSettings) -> Option { let mut out = Self { position: PlaybackPosition::new(settings, song)?, is_done: false, diff --git a/src/audio_processing/sample.rs b/src/audio_processing/sample.rs index cd647ad..4e137a7 100644 --- a/src/audio_processing/sample.rs +++ b/src/audio_processing/sample.rs @@ -1,4 +1,4 @@ -use std::ops::ControlFlow; +use std::{num::NonZero, ops::ControlFlow}; use crate::{ project::note_event::Note, @@ -51,14 +51,14 @@ pub struct SamplePlayer { // f32 ranges 0..1 position: (usize, f32), // is_done: bool, - out_rate: u32, + out_rate: NonZero, // how much the position is advanced for each output sample. // computed from in and out rate step_size: f32, } impl SamplePlayer { - pub fn new(sample: Sample, meta: SampleMetaData, out_rate: u32, note: Note) -> Self { + pub fn new(sample: Sample, meta: SampleMetaData, out_rate: NonZero, note: Note) -> Self { let step_size = Self::compute_step_size(meta.sample_rate, out_rate, meta.base_note, note); Self { sample, @@ -80,8 +80,8 @@ impl SamplePlayer { #[inline] fn compute_step_size( - in_rate: u32, - out_rate: u32, + in_rate: NonZero, + out_rate: NonZero, sample_base_note: Note, playing_note: Note, ) -> f32 { @@ -90,7 +90,7 @@ impl SamplePlayer { // manually reduced formula: 2^((play_note - sample_base_note)/12) * (outrate / inrate) // herbie (https://herbie.uwplse.org/demo/index.html) can't optimize further: https://herbie.uwplse.org/demo/e096ef89ee257ad611dd56378bd139a065a6bea0.02e7ec5a3709ad3e06968daa97db50d636f1e44b/graph.html (f32::from(i16::from(playing_note.get()) - i16::from(sample_base_note.get())) / 12.).exp2() - * (out_rate as f32 / in_rate as f32) + * (out_rate.get() as f32 / in_rate.get() as f32) } fn set_step_size(&mut self) { @@ -102,7 +102,7 @@ impl SamplePlayer { ); } - pub fn set_out_samplerate(&mut self, samplerate: u32) { + pub fn set_out_samplerate(&mut self, samplerate: NonZero) { self.out_rate = samplerate; self.set_step_size(); } @@ -137,8 +137,10 @@ impl SamplePlayer { } fn compute_linear(&mut self) -> Frame { + // There are two types that implement ProcessingFrame: f32 and Frame, so stereo and mono audio data. + // the compiler will monomorphize this function to both versions and depending on wether that sample is mono + // or stereo the correct version will be called. struct Linear(f32); - impl ProcessingFunction<2, S> for Linear { fn process(self, data: &[S; 2]) -> S { let diff = data[1] - data[0]; diff --git a/src/live_audio.rs b/src/live_audio.rs index 4a3bc87..9cc3caf 100644 --- a/src/live_audio.rs +++ b/src/live_audio.rs @@ -69,7 +69,9 @@ impl LiveAudio { let sample_player = SamplePlayer::new( sample.1.clone(), sample.0, - self.config.sample_rate / 2, + // why is this div by 2 here + // self.config.sample_rate / 2, + self.config.sample_rate, note.note, ); self.live_note = Some(sample_player); diff --git a/src/manager.rs b/src/manager.rs index c688e78..a627559 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -1,4 +1,8 @@ -use std::{fmt::Debug, num::NonZeroU16, time::Duration}; +use std::{ + fmt::Debug, + num::{NonZero, NonZeroU16}, + time::Duration, +}; use simple_left_right::{WriteGuard, Writer}; @@ -166,7 +170,7 @@ impl AudioManager { let audio_worker = LiveAudio::new(reader, to_worker.1, from_worker.0, config); let buffer_time = - Duration::from_millis((config.buffer_size * 1000 / config.buffer_size).into()); + Duration::from_millis((config.buffer_size * 1000 / config.sample_rate).into()); self.stream_comms = Some(ActiveStreamComms { buffer_time, @@ -231,7 +235,7 @@ impl SongEdit<'_> { pub struct OutputConfig { pub buffer_size: u32, pub channel_count: NonZeroU16, - pub sample_rate: u32, + pub sample_rate: NonZero, } #[derive(Debug, Clone, Copy)] diff --git a/src/sample.rs b/src/sample.rs index 999c2c5..6c1408c 100644 --- a/src/sample.rs +++ b/src/sample.rs @@ -1,6 +1,7 @@ use std::{ fmt::Debug, iter::repeat_n, + num::NonZero, ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, sync::Arc, }; @@ -55,6 +56,9 @@ impl Sample { pub(crate) fn compute< const N: usize, + // all implementations are generic over the ProcessingFrame type. here both possible ProcessingFrame types + // are required, so that it can be decided at runtime which one to call. both are generated by the compiler + // from the generic implementation Proc: ProcessingFunction + ProcessingFunction, >( &self, @@ -124,7 +128,7 @@ impl Debug for Sample { } } -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug)] pub struct SampleMetaData { pub default_volume: u8, pub global_volume: u8, @@ -133,6 +137,6 @@ pub struct SampleMetaData { pub vibrato_depth: u8, pub vibrato_rate: u8, pub vibrato_waveform: VibratoWave, - pub sample_rate: u32, + pub sample_rate: NonZero, pub base_note: Note, } -- 2.51.2