Skip to content

Commit a8dd489

Browse files
perf: CPU time, PSS
Signed-off-by: Maika Namuo <httpworldview@gmail.com>
1 parent bfe7c2b commit a8dd489

6 files changed

Lines changed: 294 additions & 102 deletions

File tree

‎src/dsp.rs‎

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -238,14 +238,28 @@ impl<'a> AudioBlock<'a> {
238238
&self,
239239
) -> impl ExactSizeIterator<Item = [f32; 2]> + DoubleEndedIterator + '_ {
240240
let matrix = self.stereo_matrix();
241-
self.samples.chunks_exact(self.channels).map(move |frame| {
242-
frame
243-
.iter()
244-
.zip(matrix)
245-
.fold([0.0; 2], |[left, right], (&sample, weights)| {
246-
[left + sample * weights[0], right + sample * weights[1]]
247-
})
248-
})
241+
self.samples
242+
.chunks_exact(self.channels)
243+
.map(move |frame| match matrix {
244+
[weights] => {
245+
let sample = frame[0];
246+
[0.0 + sample * weights[0], 0.0 + sample * weights[1]]
247+
}
248+
[first, second] => {
249+
let sample = frame[0];
250+
let [left, right] = [0.0 + sample * first[0], 0.0 + sample * first[1]];
251+
let sample = frame[1];
252+
[left + sample * second[0], right + sample * second[1]]
253+
}
254+
_ => {
255+
frame
256+
.iter()
257+
.zip(matrix)
258+
.fold([0.0; 2], |[left, right], (&sample, weights)| {
259+
[left + sample * weights[0], right + sample * weights[1]]
260+
})
261+
}
262+
})
249263
}
250264

251265
pub fn is_empty(&self) -> bool {
@@ -617,6 +631,41 @@ mod tests {
617631
);
618632
}
619633

634+
#[test]
635+
fn common_stereo_paths_preserve_general_fold_bits() {
636+
for (samples, channels) in [
637+
(
638+
&[0.0, -0.0, f32::from_bits(0x7fc0_1234), f32::INFINITY][..],
639+
1,
640+
),
641+
(
642+
&[
643+
0.0,
644+
-0.0,
645+
0.25,
646+
-0.5,
647+
f32::from_bits(0x7fc0_1234),
648+
f32::INFINITY,
649+
][..],
650+
2,
651+
),
652+
] {
653+
let block = AudioBlock::new(samples, channels, 48_000.0);
654+
let matrix = block.stereo_matrix();
655+
let expected = samples.chunks_exact(channels).map(|frame| {
656+
frame
657+
.iter()
658+
.zip(matrix)
659+
.fold([0.0; 2], |[left, right], (&sample, weights)| {
660+
[left + sample * weights[0], right + sample * weights[1]]
661+
})
662+
});
663+
for (actual, expected) in block.stereo_frames().zip(expected) {
664+
assert_eq!(actual.map(f32::to_bits), expected.map(f32::to_bits));
665+
}
666+
}
667+
}
668+
620669
#[test]
621670
fn running_means_sanitize_non_finite_values_without_poisoning_state() {
622671
let mut means = WindowedMeans::<1, 1>::new([1]);

‎src/visuals/registry.rs‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,29 @@ use std::{cell::RefCell, rc::Rc};
2222

2323
type Shared<T> = Rc<RefCell<T>>;
2424

25+
trait PrepareProcessor {
26+
fn prepare(&mut self) {}
27+
}
28+
29+
impl PrepareProcessor for loudness::LoudnessProcessor {}
30+
impl PrepareProcessor for oscilloscope::OscilloscopeProcessor {}
31+
impl PrepareProcessor for stereometer::StereometerProcessor {}
32+
impl PrepareProcessor for spectrogram::SpectrogramProcessor {
33+
fn prepare(&mut self) {
34+
spectrogram::SpectrogramProcessor::prepare(self);
35+
}
36+
}
37+
impl PrepareProcessor for spectrum::SpectrumProcessor {
38+
fn prepare(&mut self) {
39+
spectrum::SpectrumProcessor::prepare(self);
40+
}
41+
}
42+
impl PrepareProcessor for waveform::WaveformProcessor {
43+
fn prepare(&mut self) {
44+
waveform::WaveformProcessor::prepare(self);
45+
}
46+
}
47+
2548
// too many stops -> keep first N
2649
// too few stops -> copy provided, repeat last
2750
fn resolve_palette<const N: usize>(
@@ -127,6 +150,10 @@ macro_rules! visuals {
127150
self.state.borrow_mut().reset_audio();
128151
}
129152

153+
fn prepare(&mut self) {
154+
PrepareProcessor::prepare(&mut self.processor);
155+
}
156+
130157
fn content(&self) -> VisualContent {
131158
VisualContent(VisualContentInner::$variant(self.state.clone()))
132159
}
@@ -248,6 +275,7 @@ struct Visual<P, S> {
248275
pub trait VisualModule {
249276
fn ingest(&mut self, block: &AudioBlock<'_>);
250277
fn reset_audio(&mut self);
278+
fn prepare(&mut self);
251279
fn content(&self) -> VisualContent;
252280
fn apply(&mut self, settings: &ModuleSettings);
253281
fn export(&self) -> ModuleSettings;
@@ -273,6 +301,16 @@ impl Entry {
273301
self.enabled = enabled;
274302
}
275303
self.module.apply(settings);
304+
if self.enabled {
305+
self.module.prepare();
306+
}
307+
}
308+
309+
fn set_enabled(&mut self, enabled: bool) {
310+
self.enabled = enabled;
311+
if enabled {
312+
self.module.prepare();
313+
}
276314
}
277315
}
278316

@@ -360,7 +398,7 @@ impl VisualManager {
360398
}
361399
pub fn set_enabled(&mut self, kind: VisualKind, enabled: bool) {
362400
if let Some(index) = self.position(kind) {
363-
self.entries[index].enabled = enabled;
401+
self.entries[index].set_enabled(enabled);
364402
}
365403
}
366404
pub fn has_enabled(&self) -> bool {

‎src/visuals/spectrogram/processor.rs‎

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ impl SpectrogramConfig {
8080
}
8181
}
8282

83+
enum Transforms {
84+
Reassigned {
85+
analysis: Arc<dyn Fft<f32>>,
86+
hilbert_forward: Arc<dyn Fft<f32>>,
87+
hilbert_inverse: Arc<dyn Fft<f32>>,
88+
},
89+
Classic(Arc<dyn RealToComplex<f32>>),
90+
}
91+
8392
#[derive(Default)]
8493
struct ReassignmentBuffers {
8594
derivative_window: Vec<f32>,
@@ -168,10 +177,7 @@ pub struct SpectrogramUpdate {
168177

169178
pub struct SpectrogramProcessor {
170179
config: SpectrogramConfig,
171-
fft: Arc<dyn Fft<f32>>,
172-
classic_fft: Arc<dyn RealToComplex<f32>>,
173-
hilbert_fft: Arc<dyn Fft<f32>>,
174-
hilbert_ifft: Arc<dyn Fft<f32>>,
180+
transforms: Option<Transforms>,
175181
fft_size: usize,
176182
window: Arc<[f32]>,
177183
real: Vec<f32>,
@@ -190,15 +196,9 @@ pub struct SpectrogramProcessor {
190196
impl SpectrogramProcessor {
191197
pub fn new(mut cfg: SpectrogramConfig) -> Self {
192198
cfg.normalize();
193-
let mut planner = FftPlanner::new();
194-
let placeholder_fft = planner.plan_fft_forward(1024);
195-
let classic_fft = RealFftPlanner::new().plan_fft_forward(1024);
196-
let mut processor = Self {
199+
Self {
197200
config: cfg,
198-
fft: placeholder_fft.clone(),
199-
classic_fft,
200-
hilbert_fft: placeholder_fft.clone(),
201-
hilbert_ifft: placeholder_fft,
201+
transforms: None,
202202
fft_size: 0,
203203
window: Arc::from([]),
204204
real: Vec::new(),
@@ -212,9 +212,7 @@ impl SpectrogramProcessor {
212212
pending_skip_samples: 0,
213213
audio_last_nonzero: None,
214214
reset: true,
215-
};
216-
processor.rebuild_fft();
217-
processor
215+
}
218216
}
219217

220218
pub fn config(&self) -> SpectrogramConfig {
@@ -228,6 +226,12 @@ impl SpectrogramProcessor {
228226
self.reset = true;
229227
}
230228

229+
pub(in crate::visuals) fn prepare(&mut self) {
230+
if self.transforms.is_none() {
231+
self.rebuild_fft();
232+
}
233+
}
234+
231235
fn hilbert_len_for(window_size: usize) -> usize {
232236
(window_size * 2).next_power_of_two().max(2)
233237
}
@@ -239,12 +243,14 @@ impl SpectrogramProcessor {
239243
let use_reassignment = self.config.use_reassignment;
240244
let active_len = if use_reassignment { hilbert_len } else { self.fft_size };
241245
let mut planner = FftPlanner::new();
242-
self.fft = planner.plan_fft_forward(self.fft_size);
243-
self.classic_fft = RealFftPlanner::new().plan_fft_forward(self.fft_size);
244-
(self.hilbert_fft, self.hilbert_ifft) = if use_reassignment {
245-
(planner.plan_fft_forward(hilbert_len), planner.plan_fft_inverse(hilbert_len))
246+
let transforms = if use_reassignment {
247+
Transforms::Reassigned {
248+
analysis: planner.plan_fft_forward(self.fft_size),
249+
hilbert_forward: planner.plan_fft_forward(hilbert_len),
250+
hilbert_inverse: planner.plan_fft_inverse(hilbert_len),
251+
}
246252
} else {
247-
(self.fft.clone(), self.fft.clone())
253+
Transforms::Classic(RealFftPlanner::new().plan_fft_forward(self.fft_size))
248254
};
249255
self.window = window_coefficients(self.config.window, window_size);
250256
let bin_count = self.fft_size / 2 + 1;
@@ -253,15 +259,19 @@ impl SpectrogramProcessor {
253259
resize_trim(&mut self.real, active_len, 0.0);
254260
resize_trim(&mut self.hilbert_buf, reassigned_len, Complex32::ZERO);
255261
resize_trim(&mut self.spectrum, classic_bin_count, Complex32::ZERO);
256-
let scratch_len = if use_reassignment {
257-
self.fft
262+
let scratch_len = match &transforms {
263+
Transforms::Reassigned {
264+
analysis,
265+
hilbert_forward,
266+
hilbert_inverse,
267+
} => analysis
258268
.get_inplace_scratch_len()
259-
.max(self.hilbert_fft.get_inplace_scratch_len())
260-
.max(self.hilbert_ifft.get_inplace_scratch_len())
261-
} else {
262-
self.classic_fft.get_scratch_len()
269+
.max(hilbert_forward.get_inplace_scratch_len())
270+
.max(hilbert_inverse.get_inplace_scratch_len()),
271+
Transforms::Classic(fft) => fft.get_scratch_len(),
263272
};
264273
resize_trim(&mut self.scratch, scratch_len, Complex32::ZERO);
274+
self.transforms = Some(transforms);
265275
self.bin_norm = compute_fft_bin_normalization(&self.window, self.fft_size);
266276
self.reassigned_power_scale = if use_reassignment {
267277
let inv_hilbert_len = (hilbert_len as f32).recip();
@@ -325,18 +335,26 @@ impl SpectrogramProcessor {
325335
}
326336

327337
let col = if reassignment_enabled {
338+
let Some(Transforms::Reassigned {
339+
analysis,
340+
hilbert_forward,
341+
hilbert_inverse,
342+
}) = &self.transforms
343+
else {
344+
unreachable!("reassignment transforms")
345+
};
328346
copy_from_deque(&mut self.real[..read_len], &self.audio_buffer);
329347
// Use an analytic signal so low-frequency bins are not polluted
330348
// by the negative-frequency mirror of the windowed real signal.
331349
hilbert_transform(
332350
&self.real[..read_len],
333351
&mut self.hilbert_buf,
334-
&*self.hilbert_fft,
335-
&*self.hilbert_ifft,
352+
&**hilbert_forward,
353+
&**hilbert_inverse,
336354
&mut self.scratch,
337355
);
338356
let analytic = &self.hilbert_buf[center_offset..center_offset + window_size];
339-
let fft = &*self.fft;
357+
let fft = &**analysis;
340358
let r = &mut self.reassign;
341359
let (base, auxiliary) = r.spectra.split_at_mut(self.fft_size);
342360
let (derivative, time_weighted) = auxiliary.split_at_mut(self.fft_size);
@@ -357,8 +375,10 @@ impl SpectrogramProcessor {
357375
&self.window,
358376
);
359377
self.real[window_size..].fill(0.0);
360-
if self
361-
.classic_fft
378+
let Some(Transforms::Classic(fft)) = &self.transforms else {
379+
unreachable!("classic transform")
380+
};
381+
if fft
362382
.process_with_scratch(
363383
&mut self.real,
364384
&mut self.spectrum,
@@ -501,6 +521,7 @@ impl SpectrogramProcessor {
501521
self.audio_last_nonzero = None;
502522
self.reset = true;
503523
}
524+
self.prepare();
504525
self.push_audio(block);
505526
let cols = self.process_ready_windows();
506527
let bin_count = self.fft_size / 2 + 1;
@@ -523,6 +544,7 @@ impl SpectrogramProcessor {
523544
pub fn update_config(&mut self, mut cfg: SpectrogramConfig) {
524545
cfg.normalize();
525546
let prev = self.config;
547+
let prepared = self.transforms.is_some();
526548
self.config = cfg;
527549

528550
let rate_changed = prev.sample_rate != cfg.sample_rate;
@@ -532,7 +554,7 @@ impl SpectrogramProcessor {
532554
|| prev.use_reassignment != cfg.use_reassignment
533555
|| rate_changed;
534556

535-
if rebuild {
557+
if rebuild && prepared {
536558
self.rebuild_fft();
537559
if rate_changed {
538560
self.audio_buffer.clear();
@@ -789,13 +811,16 @@ mod tests {
789811

790812
#[test]
791813
fn classic_retention_budget_uses_packed_column_width() {
792-
let processor = SpectrogramProcessor::new(SpectrogramConfig {
814+
let mut processor = SpectrogramProcessor::new(SpectrogramConfig {
793815
fft_size: 16_384,
794816
zero_padding_factor: 32,
795817
history_length: MAX_SPECTROGRAM_HISTORY_COLUMNS,
796818
use_reassignment: false,
797819
..Default::default()
798820
});
821+
assert!(processor.transforms.is_none());
822+
assert!(processor.real.is_empty());
823+
processor.prepare();
799824
let bins = processor.fft_size / 2 + 1;
800825
let packed_stride = bins.div_ceil(2) * std::mem::size_of::<u32>();
801826

@@ -808,6 +833,7 @@ mod tests {
808833
#[test]
809834
fn fft_rebuild_keeps_newest_pending_audio() {
810835
let mut p = SpectrogramProcessor::new(cfg(64, 16, false));
836+
p.prepare();
811837
let samples: Vec<_> = (0..200).map(|i| i as f32).collect();
812838
p.push_audio(&AudioBlock::new(&samples, 1, DEFAULT_SAMPLE_RATE));
813839
let mut next = p.config();

0 commit comments

Comments
 (0)