dasp_rs/utils/frequency.rs
1use ndarray::Array1;
2use crate::utils::notation;
3
4/// Converts frequencies in Hz to Western musical note names.
5///
6/// # Arguments
7/// * `frequencies` - Array of frequencies in Hz
8///
9/// # Returns
10/// Returns a `Vec<String>` containing note names (e.g., "C4", "G#5").
11///
12/// # Examples
13/// ```
14/// let freqs = vec![261.63, 329.63];
15/// let notes = hz_to_note(&freqs);
16/// assert_eq!(notes, vec!["C4", "E4"]);
17/// ```
18pub fn hz_to_note(frequencies: &[f32]) -> Vec<String> {
19 frequencies.iter().map(|&f| {
20 let midi = hz_to_midi(&[f])[0];
21 midi_to_note(&[midi], None, None, None)[0].clone()
22 }).collect()
23}
24
25/// Converts frequencies in Hz to MIDI note numbers.
26///
27/// # Arguments
28/// * `frequencies` - Array of frequencies in Hz
29///
30/// # Returns
31/// Returns a `Vec<f32>` containing MIDI note numbers (A4 = 440 Hz = MIDI 69).
32///
33/// # Examples
34/// ```
35/// let freqs = vec![440.0];
36/// let midi = hz_to_midi(&freqs);
37/// assert_eq!(midi, vec![69.0]);
38/// ```
39pub fn hz_to_midi(frequencies: &[f32]) -> Vec<f32> {
40 frequencies.iter().map(|&f| 12.0 * (f / 440.0).log2() + 69.0).collect()
41}
42
43/// Converts frequencies in Hz to Hindustani svara notation.
44///
45/// # Arguments
46/// * `frequencies` - Array of frequencies in Hz
47/// * `Sa` - Frequency of the tonic (Sa) in Hz
48/// * `abbr` - Optional flag for abbreviated notation (defaults to false)
49///
50/// # Returns
51/// Returns a `Vec<String>` containing Hindustani svara names (e.g., "S", "R1" or "Shadjam", "Shuddha Rishabham").
52///
53/// # Examples
54/// ```
55/// let freqs = vec![261.63, 293.66];
56/// let svaras = hz_to_svara_h(&freqs, 261.63, Some(true));
57/// assert_eq!(svaras, vec!["S", "R2"]);
58/// ```
59pub fn hz_to_svara_h(frequencies: &[f32], sa: f32, abbr: Option<bool>) -> Vec<String> {
60 let abbr = abbr.unwrap_or(false);
61 let midi_sa = hz_to_midi(&[sa])[0];
62 let midi_notes = hz_to_midi(frequencies);
63 let svara_names = if abbr {
64 vec!["S", "R1", "R2", "G1", "G2", "M1", "M2", "P", "D1", "D2", "N1", "N2"]
65 } else {
66 vec!["Shadjam", "Shuddha Rishabham", "Chatushruti Rishabham",
67 "Shuddha Gandharam", "Sadharana Gandharam", "Shuddha Madhyamam",
68 "Prati Madhyamam", "Panchamam", "Shuddha Dhaivatam", "Chatushruti Dhaivatam",
69 "Shuddha Nishadam", "Kaisiki Nishadam"]
70 };
71 midi_notes.iter().map(|&m| {
72 let degree = ((m - midi_sa + 0.5).round() as i32 % 12 + 12) % 12;
73 svara_names[degree as usize].to_string()
74 }).collect()
75}
76
77/// Converts frequencies in Hz to Carnatic svara notation based on a melakarta raga.
78///
79/// # Arguments
80/// * `frequencies` - Array of frequencies in Hz
81/// * `Sa` - Frequency of the tonic (Sa) in Hz
82/// * `mela` - Optional melakarta raga index (1-72, defaults to 29, Dheerashankarabharanam)
83///
84/// # Returns
85/// Returns a `Vec<String>` containing Carnatic svara names (e.g., "S", "R2").
86///
87/// # Examples
88/// ```
89/// let freqs = vec![261.63, 293.66];
90/// let svaras = hz_to_svara_c(&freqs, 261.63, None);
91/// assert_eq!(svaras, vec!["S", "R2"]);
92/// ```
93pub fn hz_to_svara_c(frequencies: &[f32], sa: f32, mela: Option<usize>) -> Vec<String> {
94 let mela = mela.unwrap_or(29);
95 let degrees = notation::mela_to_degrees(mela);
96 let midi_sa = hz_to_midi(&[sa])[0];
97 let midi_notes = hz_to_midi(frequencies);
98 midi_notes.iter().map(|&m| {
99 let semitone = ((m - midi_sa + 0.5).round() as i32 % 12 + 12) % 12;
100 let idx = degrees.iter().position(|&d| d == semitone as usize).unwrap_or(0);
101 let base = match idx {
102 0 => "S", 1..=3 => "R", 4..=6 => "G", 7 => "M", 8 => "P", 9..=11 => "D", 12..=14 => "N", _ => "S",
103 };
104 let variant = match degrees[idx] % 12 {
105 1 => "1", 2 => "2", 3 => "3", 5 => "1", 6 => "2", 7 => "3", 8 => "1", 9 => "2", 10 => "3", _ => "",
106 };
107 format!("{}{}", base, variant)
108 }).collect()
109}
110
111/// Converts frequencies in Hz to Functional Just System (FJS) notation.
112///
113/// # Arguments
114/// * `frequencies` - Array of frequencies in Hz
115/// * `fmin` - Optional minimum frequency (defaults to 16.35 Hz, C0)
116/// * `unison` - Optional unison interval ratio (defaults to 1.0)
117///
118/// # Returns
119/// Returns a `Vec<String>` containing FJS note names (e.g., "C4 1/1").
120///
121/// # Examples
122/// ```
123/// let freqs = vec![261.63];
124/// let fjs = hz_to_fjs(&freqs, None, None);
125/// assert_eq!(fjs, vec!["C4 1/1"]);
126/// ```
127pub fn hz_to_fjs(frequencies: &[f32], fmin: Option<f32>, unison: Option<f32>) -> Vec<String> {
128 let fmin = fmin.unwrap_or(16.35);
129 let unison = unison.unwrap_or(1.0);
130 frequencies.iter().map(|&f| {
131 let octaves = (f / fmin).log2().floor();
132 let interval = f / (fmin * 2.0f32.powf(octaves)) / unison;
133 let ratio = notation::interval_to_fjs(interval, Some(1.0));
134 format!("C{} {}", octaves as i32, ratio)
135 }).collect()
136}
137
138/// Converts MIDI note numbers to frequencies in Hz.
139///
140/// # Arguments
141/// * `notes` - Array of MIDI note numbers
142///
143/// # Returns
144/// Returns a `Vec<f32>` containing frequencies in Hz (A4 = MIDI 69 = 440 Hz).
145///
146/// # Examples
147/// ```
148/// let midi = vec![69.0];
149/// let freqs = midi_to_hz(&midi);
150/// assert_eq!(freqs, vec![440.0]);
151/// ```
152pub fn midi_to_hz(notes: &[f32]) -> Vec<f32> {
153 notes.iter().map(|&n| 440.0 * 2.0f32.powf((n - 69.0) / 12.0)).collect()
154}
155
156/// Converts MIDI note numbers to Western musical note names.
157///
158/// # Arguments
159/// * `midi` - Array of MIDI note numbers
160/// * `octave` - Optional flag to include octave number (defaults to true)
161/// * `_cents` - Optional flag for cents (unused, defaults to None)
162/// * `_key` - Optional key signature (unused, defaults to None)
163///
164/// # Returns
165/// Returns a `Vec<String>` containing note names (e.g., "C4", "G#").
166///
167/// # Examples
168/// ```
169/// let midi = vec![60.0, 61.0];
170/// let notes = midi_to_note(&midi, None, None, None);
171/// assert_eq!(notes, vec!["C4", "C#4"]);
172/// ```
173pub fn midi_to_note(midi: &[f32], octave: Option<bool>, _cents: Option<bool>, _key: Option<&str>) -> Vec<String> {
174 let note_names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
175 midi.iter().map(|&m| {
176 let note_idx = (m.round() as usize) % 12;
177 let oct = if octave.unwrap_or(true) { format!("{}", (m.round() as i32 - 12) / 12) } else { "".to_string() };
178 format!("{}{}", note_names[note_idx], oct)
179 }).collect()
180}
181
182/// Converts MIDI note numbers to Hindustani svara notation.
183///
184/// # Arguments
185/// * `midi` - Array of MIDI note numbers
186/// * `Sa` - Frequency of the tonic (Sa) in Hz
187/// * `abbr` - Optional flag for abbreviated notation (defaults to false)
188/// * `octave` - Optional flag to include octave number (defaults to false)
189///
190/// # Returns
191/// Returns a `Vec<String>` containing Hindustani svara names with optional octave.
192///
193/// # Examples
194/// ```
195/// let midi = vec![60.0, 62.0];
196/// let svaras = midi_to_svara_h(&midi, 261.63, Some(true), None);
197/// assert_eq!(svaras, vec!["S", "R2"]);
198/// ```
199pub fn midi_to_svara_h(midi: &[f32], sa: f32, abbr: Option<bool>, octave: Option<bool>) -> Vec<String> {
200 let abbr = abbr.unwrap_or(false);
201 let octave = octave.unwrap_or(false);
202 let midi_sa = hz_to_midi(&[sa])[0];
203 let svara_names = if abbr {
204 vec!["S", "R1", "R2", "G1", "G2", "M1", "M2", "P", "D1", "D2", "N1", "N2"]
205 } else {
206 vec!["Shadjam", "Shuddha Rishabham", "Chatushruti Rishabham",
207 "Shuddha Gandharam", "Sadharana Gandharam", "Shuddha Madhyamam",
208 "Prati Madhyamam", "Panchamam", "Shuddha Dhaivatam", "Chatushruti Dhaivatam",
209 "Shuddha Nishadam", "Kaisiki Nishadam"]
210 };
211 midi.iter().map(|&m| {
212 let degree = ((m - midi_sa + 0.5).round() as i32 % 12 + 12) % 12;
213 let oct = if octave { format!("{}", (m - midi_sa).round() as i32 / 12) } else { "".to_string() };
214 format!("{}{}", svara_names[degree as usize], oct)
215 }).collect()
216}
217
218/// Converts MIDI note numbers to Carnatic svara notation based on a melakarta raga.
219///
220/// # Arguments
221/// * `midi` - Array of MIDI note numbers
222/// * `Sa` - Frequency of the tonic (Sa) in Hz
223/// * `mela` - Optional melakarta raga index (1-72, defaults to 29)
224/// * `abbr` - Optional flag for abbreviated notation (defaults to false)
225///
226/// # Returns
227/// Returns a `Vec<String>` containing Carnatic svara names.
228///
229/// # Examples
230/// ```
231/// let midi = vec![60.0, 62.0];
232/// let svaras = midi_to_svara_c(&midi, 261.63, None, Some(true));
233/// assert_eq!(svaras, vec!["S", "R2"]);
234/// ```
235pub fn midi_to_svara_c(midi: &[f32], sa: f32, mela: Option<usize>, abbr: Option<bool>) -> Vec<String> {
236 let mela = mela.unwrap_or(29);
237 let abbr = abbr.unwrap_or(false);
238 let degrees = notation::mela_to_degrees(mela);
239 let midi_sa = hz_to_midi(&[sa])[0];
240 midi.iter().map(|&m| {
241 let semitone = ((m - midi_sa + 0.5).round() as i32 % 12 + 12) % 12;
242 let idx = degrees.iter().position(|&d| d == semitone as usize).unwrap_or(0);
243 let base = match idx {
244 0 => "S", 1..=3 => "R", 4..=6 => "G", 7 => "M", 8 => "P", 9..=11 => "D", 12..=14 => "N", _ => "S",
245 };
246 let variant = match degrees[idx] % 12 {
247 1 => "1", 2 => "2", 3 => "3", 5 => "1", 6 => "2", 7 => "3", 8 => "1", 9 => "2", 10 => "3", _ => "",
248 };
249 if abbr { format!("{}{}", base, variant) } else { notation::mela_to_svara(mela, Some(false), Some(false))[idx].clone() }
250 }).collect()
251}
252
253/// Converts note names to frequencies in Hz.
254///
255/// # Arguments
256/// * `note` - Array of note names (e.g., "C4", "G#5")
257///
258/// # Returns
259/// Returns a `Vec<f32>` containing frequencies in Hz.
260///
261/// # Examples
262/// ```
263/// let notes = vec!["C4", "E4"];
264/// let freqs = note_to_hz(¬es);
265/// assert!(freqs[0] > 261.0 && freqs[0] < 262.0);
266/// ```
267pub fn note_to_hz(note: &[&str]) -> Vec<f32> {
268 note.iter().map(|&n| {
269 let midi = note_to_midi(&[n], None)[0];
270 midi_to_hz(&[midi])[0]
271 }).collect()
272}
273
274/// Converts note names to MIDI note numbers.
275///
276/// # Arguments
277/// * `note` - Array of note names (e.g., "C4", "G#5")
278/// * `round_midi` - Optional flag to round MIDI numbers (defaults to true)
279///
280/// # Returns
281/// Returns a `Vec<f32>` containing MIDI note numbers.
282///
283/// # Examples
284/// ```
285/// let notes = vec!["C4", "C#4"];
286/// let midi = note_to_midi(¬es, None);
287/// assert_eq!(midi, vec![60.0, 61.0]);
288/// ```
289pub fn note_to_midi(note: &[&str], round_midi: Option<bool>) -> Vec<f32> {
290 let note_map = [("C", 0), ("C#", 1), ("Db", 1), ("D", 2), ("D#", 3), ("Eb", 3), ("E", 4), ("F", 5), ("F#", 6), ("Gb", 6), ("G", 7), ("G#", 8), ("Ab", 8), ("A", 9), ("A#", 10), ("Bb", 10), ("B", 11)];
291 note.iter().map(|&n| {
292 let (note_part, octave_part) = n.split_at(n.find(|c: char| c.is_ascii_digit()).unwrap_or(n.len()));
293 let note_val = note_map.iter().find(|&&(name, _)| name == note_part).map(|&(_, val)| val).unwrap_or(0) as f32;
294 let octave = octave_part.parse::<i32>().unwrap_or(4);
295 let midi = note_val + (octave + 1) as f32 * 12.0;
296 if round_midi.unwrap_or(true) { midi.round() } else { midi }
297 }).collect()
298}
299
300/// Converts note names to Hindustani svara notation.
301///
302/// # Arguments
303/// * `notes` - Array of note names (e.g., "C4", "D4")
304/// * `Sa` - Frequency of the tonic (Sa) in Hz
305/// * `abbr` - Optional flag for abbreviated notation (defaults to false)
306///
307/// # Returns
308/// Returns a `Vec<String>` containing Hindustani svara names.
309///
310/// # Examples
311/// ```
312/// let notes = vec!["C4", "D4"];
313/// let svaras = note_to_svara_h(¬es, 261.63, Some(true));
314/// assert_eq!(svaras, vec!["S", "R2"]);
315/// ```
316pub fn note_to_svara_h(notes: &[&str], sa: f32, abbr: Option<bool>) -> Vec<String> {
317 let midi = note_to_midi(notes, Some(true));
318 hz_to_svara_h(&midi_to_hz(&midi), sa, abbr)
319}
320
321/// Converts note names to Carnatic svara notation.
322///
323/// # Arguments
324/// * `notes` - Array of note names (e.g., "C4", "D4")
325/// * `Sa` - Frequency of the tonic (Sa) in Hz
326/// * `mela` - Optional melakarta raga index (1-72, defaults to 29)
327/// * `abbr` - Optional flag for abbreviated notation (defaults to false)
328///
329/// # Returns
330/// Returns a `Vec<String>` containing Carnatic svara names.
331///
332/// # Examples
333/// ```
334/// let notes = vec!["C4", "D4"];
335/// let svaras = note_to_svara_c(¬es, 261.63, None, Some(true));
336/// assert_eq!(svaras, vec!["S", "R2"]);
337/// ```
338pub fn note_to_svara_c(notes: &[&str], sa: f32, mela: Option<usize>, _abbr: Option<bool>) -> Vec<String> {
339 let midi = note_to_midi(notes, Some(true));
340 hz_to_svara_c(&midi_to_hz(&midi), sa, mela)
341}
342
343/// Converts frequencies in Hz to mel scale.
344///
345/// # Arguments
346/// * `frequencies` - Array of frequencies in Hz
347/// * `htk` - Optional flag for HTK formula (defaults to false)
348///
349/// # Returns
350/// Returns a `Vec<f32>` containing mel values.
351///
352/// # Examples
353/// ```
354/// let freqs = vec![440.0];
355/// let mels = hz_to_mel(&freqs, None);
356/// ```
357pub fn hz_to_mel(frequencies: &[f32], htk: Option<bool>) -> Vec<f32> {
358 if htk.unwrap_or(false) {
359 frequencies.iter().map(|&f| 2595.0 * (1.0 + f / 700.0).log10()).collect()
360 } else {
361 frequencies.iter().map(|&f| 1125.0 * (1.0 + f / 700.0).ln()).collect()
362 }
363}
364
365/// Converts frequencies in Hz to octave numbers.
366///
367/// # Arguments
368/// * `frequencies` - Array of frequencies in Hz
369/// * `tuning` - Optional tuning adjustment in semitones (defaults to 0.0)
370///
371/// # Returns
372/// Returns a `Vec<f32>` containing octave numbers (A4 = 440 Hz = octave 4).
373///
374/// # Examples
375/// ```
376/// let freqs = vec![440.0];
377/// let octs = hz_to_octs(&freqs, None);
378/// assert_eq!(octs, vec![4.0]);
379/// ```
380pub fn hz_to_octs(frequencies: &[f32], tuning: Option<f32>) -> Vec<f32> {
381 let tune = tuning.unwrap_or(0.0);
382 frequencies.iter().map(|&f| (f / (440.0 * 2.0f32.powf(tune / 12.0))).log2() + 4.0).collect()
383}
384
385/// Converts mel values to frequencies in Hz.
386///
387/// # Arguments
388/// * `mels` - Array of mel values
389/// * `htk` - Optional flag for HTK formula (defaults to false)
390///
391/// # Returns
392/// Returns a `Vec<f32>` containing frequencies in Hz.
393///
394/// # Examples
395/// ```
396/// let mels = vec![1125.0];
397/// let freqs = mel_to_hz(&mels, None);
398/// ```
399pub fn mel_to_hz(mels: &[f32], htk: Option<bool>) -> Vec<f32> {
400 if htk.unwrap_or(false) {
401 mels.iter().map(|&m| 700.0 * (10.0f32.powf(m / 2595.0) - 1.0)).collect()
402 } else {
403 mels.iter().map(|&m| 700.0 * (m / 1125.0).exp() - 700.0).collect()
404 }
405}
406
407/// Converts octave numbers to frequencies in Hz.
408///
409/// # Arguments
410/// * `octs` - Array of octave numbers
411/// * `tuning` - Optional tuning adjustment in semitones (defaults to 0.0)
412/// * `_bins_per_octave` - Optional bins per octave (unused, defaults to None)
413///
414/// # Returns
415/// Returns a `Vec<f32>` containing frequencies in Hz.
416///
417/// # Examples
418/// ```
419/// let octs = vec![4.0];
420/// let freqs = octs_to_hz(&octs, None, None);
421/// assert_eq!(freqs, vec![440.0]);
422/// ```
423pub fn octs_to_hz(octs: &[f32], tuning: Option<f32>, _bins_per_octave: Option<usize>) -> Vec<f32> {
424 let tune = tuning.unwrap_or(0.0);
425 octs.iter().map(|&o| 440.0 * 2.0f32.powf(o - 4.0 + tune / 12.0)).collect()
426}
427
428/// Converts an A4 frequency to a tuning offset in semitones.
429///
430/// # Arguments
431/// * `A4` - Frequency of A4 in Hz
432/// * `_bins_per_octave` - Optional bins per octave (unused, defaults to None)
433///
434/// # Returns
435/// Returns a `f32` representing the tuning offset from 440 Hz in semitones.
436///
437/// # Examples
438/// ```
439/// let tuning = A4_to_tuning(432.0, None);
440/// assert!(tuning < 0.0);
441/// ```
442pub fn a4_to_tuning(a4: f32, _bins_per_octave: Option<usize>) -> f32 {
443 12.0 * (a4 / 440.0).log2()
444}
445
446/// Converts a tuning offset in semitones to an A4 frequency.
447///
448/// # Arguments
449/// * `tuning` - Tuning offset in semitones
450/// * `_bins_per_octave` - Optional bins per octave (unused, defaults to None)
451///
452/// # Returns
453/// Returns a `f32` representing the A4 frequency in Hz.
454///
455/// # Examples
456/// ```
457/// let A4 = tuning_to_A4(-0.317667, None);
458/// assert!(A4 > 431.0 && A4 < 433.0);
459/// ```
460pub fn tuning_to_a4(tuning: f32, _bins_per_octave: Option<usize>) -> f32 {
461 440.0 * 2.0f32.powf(tuning / 12.0)
462}
463
464/// Generates FFT frequency bins.
465///
466/// # Arguments
467/// * `sr` - Optional sample rate in Hz (defaults to 44100)
468/// * `n_fft` - Optional FFT size (defaults to 2048)
469///
470/// # Returns
471/// Returns a `Vec<f32>` containing frequency bins from 0 to Nyquist (sr/2).
472///
473/// # Examples
474/// ```
475/// let freqs = fft_frequencies(None, Some(4));
476/// assert_eq!(freqs, vec![0.0, 11025.0, 22050.0]);
477/// ```
478pub fn fft_frequencies(sr: Option<u32>, n_fft: Option<usize>) -> Vec<f32> {
479 let sample_rate = sr.unwrap_or(44100);
480 let n = n_fft.unwrap_or(2048);
481 Array1::linspace(0.0, sample_rate as f32 / 2.0, n / 2 + 1).to_vec()
482}
483
484/// Generates Constant-Q Transform (CQT) frequency bins.
485///
486/// # Arguments
487/// * `n_bins` - Number of frequency bins
488/// * `fmin` - Optional minimum frequency in Hz (defaults to 32.70, C1)
489///
490/// # Returns
491/// Returns a `Vec<f32>` containing CQT frequency bins.
492///
493/// # Examples
494/// ```
495/// let freqs = cqt_frequencies(3, None);
496/// ```
497pub fn cqt_frequencies(n_bins: usize, fmin: Option<f32>) -> Vec<f32> {
498 let fmin = fmin.unwrap_or(32.70);
499 let bins_per_octave = 12;
500 (0..n_bins).map(|k| fmin * 2.0f32.powf(k as f32 / bins_per_octave as f32)).collect()
501}
502
503/// Generates mel-scale frequency bins.
504///
505/// # Arguments
506/// * `n_mels` - Optional number of mel bins (defaults to 128)
507/// * `fmin` - Optional minimum frequency in Hz (defaults to 0.0)
508/// * `fmax` - Optional maximum frequency in Hz (defaults to 11025.0)
509/// * `_htk` - Optional flag for HTK formula (unused, defaults to None)
510///
511/// # Returns
512/// Returns a `Vec<f32>` containing mel-scale frequency bins.
513///
514/// # Examples
515/// ```
516/// let freqs = mel_frequencies(Some(3), None, None, None);
517/// ```
518pub fn mel_frequencies(n_mels: Option<usize>, fmin: Option<f32>, fmax: Option<f32>, _htk: Option<bool>) -> Vec<f32> {
519 let n = n_mels.unwrap_or(128);
520 let min_freq = fmin.unwrap_or(0.0);
521 let max_freq = fmax.unwrap_or(11025.0);
522 let min_mel = hz_to_mel(&[min_freq], None)[0];
523 let max_mel = hz_to_mel(&[max_freq], None)[0];
524 let mel_steps = Array1::linspace(min_mel, max_mel, n);
525 mel_to_hz(&mel_steps.to_vec(), None)
526}
527
528/// Generates tempo-related frequency bins.
529///
530/// # Arguments
531/// * `n_bins` - Number of frequency bins
532/// * `hop_length` - Optional hop length in samples (defaults to 512)
533/// * `sr` - Optional sample rate in Hz (defaults to 44100)
534///
535/// # Returns
536/// Returns a `Vec<f32>` containing tempo frequencies in beats per minute (BPM).
537///
538/// # Examples
539/// ```
540/// let freqs = tempo_frequencies(3, None, None);
541/// ```
542pub fn tempo_frequencies(n_bins: usize, hop_length: Option<usize>, sr: Option<u32>) -> Vec<f32> {
543 let sr = sr.unwrap_or(44100);
544 let hop = hop_length.unwrap_or(512);
545 let frame_rate = sr as f32 / hop as f32;
546 Array1::linspace(0.0, frame_rate / 2.0, n_bins).mapv(|f| f * 60.0).to_vec()
547}
548
549/// Generates Fourier tempo frequency bins.
550///
551/// # Arguments
552/// * `sr` - Optional sample rate in Hz (defaults to 44100)
553///
554/// # Returns
555/// Returns a `Vec<f32>` containing tempo frequencies in BPM with fixed parameters.
556///
557/// # Examples
558/// ```
559/// let freqs = fourier_tempo_frequencies(None);
560/// ```
561pub fn fourier_tempo_frequencies(sr: Option<u32>) -> Vec<f32> {
562 let sr = sr.unwrap_or(44100);
563 let hop_length = 512;
564 let n_bins = 256;
565 let frame_rate = sr as f32 / hop_length as f32;
566 Array1::linspace(0.0, frame_rate / 2.0, n_bins).mapv(|f| f * 60.0).to_vec()
567}