use std::ffi::OsStr;
use std::fs::File;
+use std::iter::{repeat, repeat_n};
use std::path::PathBuf;
+use std::sync::Arc;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread::sleep;
+use std::time::Duration;
use clap::Parser;
-use rustfft::FftPlanner;
+use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
+use eyre::{OptionExt, bail};
+use realfft::RealToComplex;
use rustfft::num_complex::Complex;
-use symphonia::core::formats::FormatOptions;
+use symphonia::core::audio::conv::IntoSample;
+use symphonia::core::audio::{Audio, AudioBuffer};
+use symphonia::core::codecs::CodecParameters;
+use symphonia::core::codecs::audio::AudioDecoderOptions;
use symphonia::core::formats::probe::Hint;
+use symphonia::core::formats::{FormatOptions, TrackType};
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
struct Args {
#[arg(help = "The file you want to make a sheetmusic of")]
file: PathBuf,
+ #[arg(
+ short,
+ long,
+ help = "Only read the file audio",
+ default_value_t = false
+ )]
+ file_audio_only: bool,
}
-const FFT_BUF_SIZE: usize = 16;
-
-trait AudioTransformer {
- const MINIMUM_BUF_DURATION: f32;
- fn apply(&mut self, buf: &mut [Complex<f32>], buf_duration: f32);
-}
+const FFT_BUF_SIZE: usize = 4096;
fn main() -> eyre::Result<()> {
let args = Args::parse();
let mut probe = symphonia::default::get_probe().probe(&hint, mss, fmt_opts, meta_opts)?;
- /*
- let mut buf: [Complex<f32>; FFT_BUF_SIZE] = std::array::from_fn(|i| {
- if i % 2 == 0 {
- Complex { re: 1.0, im: 0.0 }
- } else {
- Complex { re: 0.0, im: 0.0 }
+ let track = probe
+ .default_track(TrackType::Audio)
+ .ok_or_eyre("No track found in file")?;
+
+ let dec_opts = AudioDecoderOptions::default();
+
+ let CodecParameters::Audio(codec_params) = track
+ .codec_params
+ .as_ref()
+ .ok_or_eyre("No codec params for track")?
+ else {
+ bail!("Codec params for track aren't audio");
+ };
+
+ let mut decoder =
+ symphonia::default::get_codecs().make_audio_decoder(&codec_params, &dec_opts)?;
+
+ let track_id = track.id;
+
+ let mut sample_rate = None;
+
+ let mut samples = Vec::<f32>::new();
+ let mut f32_buf = None;
+
+ while let Some(packet) = probe.next_packet()? {
+ if packet.track_id != track_id {
+ continue;
+ }
+
+ let decoded = decoder.decode(&packet)?;
+
+ sample_rate = Some(decoded.spec().rate());
+ let f32_buf =
+ f32_buf.get_or_insert_with(|| AudioBuffer::<f32>::new(decoded.spec().clone(), 0));
+ f32_buf.grow_capacity(decoded.samples_planar());
+ f32_buf.clear();
+ f32_buf.resize_uninit(decoded.samples_planar());
+ decoded.copy_to(f32_buf);
+ let start = samples.len();
+ samples.extend((0..decoded.samples_planar()).map(|_| 0.0));
+ samples[start..].clone_from_slice(f32_buf.plane(0).ok_or_eyre("No audio plane in file")?);
+ }
+
+ let rate = sample_rate.ok_or_eyre("Empty audio file")?;
+
+ let host = cpal::default_host();
+ let device = host
+ .default_input_device()
+ .ok_or_eyre("No output device found")?;
+ let config = device
+ .supported_output_configs()?
+ .next()
+ .ok_or_eyre("No supported output config")?;
+ let supported_config = config
+ .try_with_sample_rate(rate)
+ .ok_or_eyre("Invalid supported sample rates")?;
+
+ let mut notes_map: Vec<(f32, Vec<f32>)> = Vec::new();
+ // We start at A1
+ let mut curr_note = 440.0 / 8.0;
+ let mul = 2.0f32.powf(1.0 / 12.0);
+ for _ in 0..76 {
+ notes_map.push((curr_note, Vec::new()));
+ curr_note *= mul;
+ }
+
+ let fft_ratio = 512;
+ let mut fft = FFT::new(FFT_BUF_SIZE);
+ for window in fft.all_windows(&samples, FFT_BUF_SIZE / fft_ratio) {
+ let sample = fft.process(window, rate)?;
+ for (frequency, buf) in &mut notes_map {
+ buf.push(sample(*frequency))
}
- });
- dbg!(buf);
- let mut fft_planner = FftPlanner::new();
- let fft = fft_planner.plan_fft_forward(FFT_BUF_SIZE);
- fft.process(&mut buf);
- dbg!(buf);
- */
-
- Ok(())
+ }
+
+ if !args.file_audio_only {
+ for dst in samples.iter_mut() {
+ *dst = 0.0;
+ }
+ for (frequency, dat) in ¬es_map {
+ let mul = frequency / (rate as f32) * 2.0 * std::f32::consts::PI;
+ for (i, dst) in samples.iter_mut().enumerate() {
+ let intensity = dat[i * fft_ratio / FFT_BUF_SIZE];
+ let pos = (i as f32) * mul;
+ *dst += pos.sin() * intensity / 16.0;
+ }
+ }
+ }
+
+ let state = Arc::new(SharedState::default());
+ let state2 = Arc::clone(&state);
+ let stream = device.build_output_stream(
+ &supported_config.into(),
+ move |data: &mut [i16], _: &cpal::OutputCallbackInfo| {
+ let state = &state2;
+ for (dst, src) in data.iter_mut().zip(
+ samples
+ .get(state.timestamp.load(Ordering::Relaxed)..)
+ .unwrap_or(&[])
+ .iter()
+ .chain(repeat(&0.0)),
+ ) {
+ *dst = <f32 as IntoSample<i16>>::into_sample(*src);
+ }
+ state.timestamp.fetch_add(data.len(), Ordering::Relaxed);
+ },
+ move |_err| {},
+ None,
+ )?;
+ stream.play()?;
+ loop {
+ sleep(Duration::from_secs(1000));
+ }
+}
+
+#[derive(Default)]
+struct SharedState {
+ timestamp: AtomicUsize,
+}
+
+struct FFT {
+ window: Vec<f32>,
+ input: Vec<f32>,
+ fft: Arc<dyn RealToComplex<f32>>,
+ output: Vec<Complex<f32>>,
+ scratch: Vec<Complex<f32>>,
+}
+
+impl FFT {
+ fn all_windows<'t>(
+ &'_ self,
+ slice: &'t [f32],
+ sample_spacing: usize,
+ ) -> impl Iterator<Item = impl Iterator<Item = f32> + 't> + 't {
+ let len = self.len();
+ (0..(slice.len()))
+ .filter(move |i| i % sample_spacing == 0)
+ .map(move |window| {
+ (window..(window + len)).map(move |i: usize| {
+ i.checked_sub(len)
+ .and_then(|i| slice.get(i).copied())
+ .unwrap_or(0.0)
+ })
+ })
+ }
+ fn new(len: usize) -> Self {
+ let fft = realfft::RealFftPlanner::new().plan_fft_forward(len);
+ Self {
+ window: (0..len)
+ .map(|i| {
+ let pos = (i as f32) / (len - 1) as f32;
+ 0.5 * (1.0 - f32::cos(std::f32::consts::PI * 2.0 * pos))
+ })
+ .collect(),
+ input: fft.make_input_vec(),
+ output: fft.make_output_vec(),
+ scratch: fft.make_scratch_vec(),
+ fft,
+ }
+ }
+ fn process<'t>(
+ &'t mut self,
+ samples: impl IntoIterator<Item = f32>,
+ hz: u32,
+ ) -> eyre::Result<impl Fn(f32) -> f32 + 't> {
+ self.input.clear();
+ self.input
+ .extend(samples.into_iter().zip(&self.window).map(|(a, b)| a * *b));
+ self.fft
+ .process_with_scratch(&mut self.input, &mut self.output, &mut self.scratch)?;
+ let mul = 1.0 / (self.len() as f32).sqrt();
+ let self2 = &*self;
+ let discrete_query = move |i: usize| (self2.output[i] * mul).norm();
+ let hz_to_idx = move |query_hz: f32| (self2.len() as f32 * query_hz / (hz as f32)) as usize;
+ Ok(move |query_hz| discrete_query(hz_to_idx(query_hz)))
+ }
+ fn len(&self) -> usize {
+ self.window.len()
+ }
}