#[repr(C)]pub struct FftBin {
pub re: f32,
pub im: f32,
}Expand description
One frequency bin: a complex number, single precision.
Laid out as NE10 lays out its own, so a slice of these is handed to
the transform as it stands rather than converted. Not
num_complex’s Complex32, which this crate would then owe a
major version to; the fields are public and the conversions cheap.
Fields§
§re: f32The real part.
im: f32The imaginary part.
Implementations§
Source§impl FftBin
impl FftBin
Sourcepub const ZERO: Self
pub const ZERO: Self
The origin: both parts zero.
The same value Default gives, in a form a const item and
an array initialiser can use.
Sourcepub fn magnitude(self) -> f32
pub fn magnitude(self) -> f32
The magnitude, sqrt(re² + im²).
The same quantity Bela’s Fft::fda reports, not the same
bits: that one uses sqrtf_neon, an approximation from
libraries/math_neon, and short-circuits to 0 when both parts
are zero.
Examples found in repository?
276 fn analyse(&mut self, context: &BlockContext) {
277 let Some(analysis) = &mut self.analysis else {
278 return;
279 };
280 if context.audio_in_channels() == 0 {
281 return;
282 }
283
284 for frame in 0..context.audio_frames() {
285 analysis.window[analysis.filled] = context.audio_read(frame, 0);
286 analysis.filled += 1;
287 if analysis.filled < analysis.window.len() {
288 continue;
289 }
290 analysis.filled = 0;
291
292 let transformed = {
293 let _section = analysis.timer.measure();
294 analysis
295 .fft
296 .forward(&mut analysis.window, &mut analysis.spectrum)
297 };
298 if transformed.is_err() {
299 // Both buffers came from the plan, so their lengths
300 // agree by construction; saying so beats going quiet
301 // if a later edit changes one. Once per window rather
302 // than once per block, which is why this one prints
303 // where the measurement below does not.
304 rt_println!("render_post: the analysis buffers no longer fit the plan");
305 continue;
306 }
307
308 // The loudest bin, skipping DC — which a little offset on
309 // the input would otherwise win every time.
310 let peak = analysis
311 .spectrum
312 .iter()
313 .enumerate()
314 .skip(1)
315 .max_by(|(_, a), (_, b)| a.magnitude_squared().total_cmp(&b.magnitude_squared()));
316 if let Some((bin, value)) = peak {
317 #[allow(
318 clippy::cast_precision_loss,
319 reason = "a bin index is far below f32's exact integer range"
320 )]
321 let hz = self.sample_rate * bin as f32 / analysis_length_as_float();
322 self.published
323 .peak_hz
324 .store(hz.to_bits(), Ordering::Relaxed);
325 self.published
326 .peak_magnitude
327 .store(value.magnitude().to_bits(), Ordering::Relaxed);
328 }
329 }
330 }Sourcepub const fn magnitude_squared(self) -> f32
pub const fn magnitude_squared(self) -> f32
The magnitude squared, re² + im².
What to compare bins with: it orders them the same way
magnitude does and has no square root in
it.
Examples found in repository?
276 fn analyse(&mut self, context: &BlockContext) {
277 let Some(analysis) = &mut self.analysis else {
278 return;
279 };
280 if context.audio_in_channels() == 0 {
281 return;
282 }
283
284 for frame in 0..context.audio_frames() {
285 analysis.window[analysis.filled] = context.audio_read(frame, 0);
286 analysis.filled += 1;
287 if analysis.filled < analysis.window.len() {
288 continue;
289 }
290 analysis.filled = 0;
291
292 let transformed = {
293 let _section = analysis.timer.measure();
294 analysis
295 .fft
296 .forward(&mut analysis.window, &mut analysis.spectrum)
297 };
298 if transformed.is_err() {
299 // Both buffers came from the plan, so their lengths
300 // agree by construction; saying so beats going quiet
301 // if a later edit changes one. Once per window rather
302 // than once per block, which is why this one prints
303 // where the measurement below does not.
304 rt_println!("render_post: the analysis buffers no longer fit the plan");
305 continue;
306 }
307
308 // The loudest bin, skipping DC — which a little offset on
309 // the input would otherwise win every time.
310 let peak = analysis
311 .spectrum
312 .iter()
313 .enumerate()
314 .skip(1)
315 .max_by(|(_, a), (_, b)| a.magnitude_squared().total_cmp(&b.magnitude_squared()));
316 if let Some((bin, value)) = peak {
317 #[allow(
318 clippy::cast_precision_loss,
319 reason = "a bin index is far below f32's exact integer range"
320 )]
321 let hz = self.sample_rate * bin as f32 / analysis_length_as_float();
322 self.published
323 .peak_hz
324 .store(hz.to_bits(), Ordering::Relaxed);
325 self.published
326 .peak_magnitude
327 .store(value.magnitude().to_bits(), Ordering::Relaxed);
328 }
329 }
330 }