showViewer 0.1.1

a simple vj loop renderer
use realfft::RealFftPlanner;
use rfd::FileDialog;
use std::fs::File;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use symphonia::core::audio::SampleBuffer;
use symphonia::core::codecs::{CODEC_TYPE_NULL, DecoderOptions};
use symphonia::core::errors::Error as SymphoniaError;
use symphonia::core::formats::FormatOptions;
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;

pub fn open_file() -> Option<PathBuf> {
    FileDialog::new()
        .add_filter("audio", &["mp3", "wav", "flac"])
        .set_directory("/")
        .pick_file()
}

pub fn analyze(path: &Path) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
    let file = File::open(path)?;
    let mms = MediaSourceStream::new(Box::new(file), Default::default());
    let mut hint = Hint::new();
    if let Some(extension) = path.extension().and_then(|extension| extension.to_str()) {
        hint.with_extension(extension);
    }

    let probed = symphonia::default::get_probe().format(
        &hint,
        mms,
        &FormatOptions::default(),
        &MetadataOptions::default(),
    )?;

    let mut format = probed.format;
    let (track_id, codec_params) = format
        .tracks()
        .iter()
        .find(|track| track.codec_params.codec != CODEC_TYPE_NULL)
        .map(|track| (track.id, track.codec_params.clone()))
        .ok_or_else(|| {
            std::io::Error::new(ErrorKind::InvalidData, "no supported audio track found")
        })?;
    let mut decoder =
        symphonia::default::get_codecs().make(&codec_params, &DecoderOptions::default())?;

    let mut samples = Vec::new();
    loop {
        let packet = match format.next_packet() {
            Ok(packet) => packet,
            Err(SymphoniaError::IoError(error)) if error.kind() == ErrorKind::UnexpectedEof => {
                break;
            }
            Err(error) => return Err(Box::new(error)),
        };

        if packet.track_id() != track_id {
            continue;
        }

        match decoder.decode(&packet) {
            Ok(decoded) => {
                let mut sample_buffer =
                    SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
                sample_buffer.copy_interleaved_ref(decoded);
                samples.extend_from_slice(sample_buffer.samples());
            }
            Err(SymphoniaError::DecodeError(_)) => continue,
            Err(error) => return Err(Box::new(error)),
        }
    }

    Ok(samples)
}

pub fn process(
    f32_samples: &[f32],
    block_size: usize,
) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
    if block_size == 0 {
        return Err(std::io::Error::new(
            ErrorKind::InvalidInput,
            "block size must be greater than zero",
        )
        .into());
    }

    let mut real_planner = RealFftPlanner::<f32>::new();
    let r2c = real_planner.plan_fft_forward(block_size);
    let mut input = r2c.make_input_vec();
    let mut spectrum = r2c.make_output_vec();
    let mut magnitudes = Vec::with_capacity(
        f32_samples
            .len()
            .div_ceil(block_size)
            .saturating_mul(spectrum.len()),
    );

    for block in f32_samples.chunks(block_size) {
        input.fill(0.0);
        input[..block.len()].copy_from_slice(block);
        r2c.process(&mut input, &mut spectrum)
            .map_err(|error| std::io::Error::new(ErrorKind::InvalidInput, error.to_string()))?;
        magnitudes.extend(spectrum.iter().map(|bin| bin.norm()));
    }

    Ok(magnitudes)
}